1.使用synchronized实现同步方法
如果一个对象已用synchronized关键字声明,那么只有一个执行线程被允许访问它。如果其他某个线程试图访问这个对象的其它方法,它将被挂起,直到第一个线程执行完正在运行的方法。
静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个执行线程访问,但是其它的线程可以访问这个对象的两个不同的synchronized方法,即其中一个是静态方法,另一个是非静态方法。如果两个方法都改变了相同的数据,将会出现数据不一致的错误。
public class Account {
private double balance;
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public synchronized void addAmount(double amount) {
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tmp += amount;
balance = tmp;
}
public synchronized void subtractAmount(double amount) {
double tmp = balance;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
tmp -= amount;
balance = tmp;
}
}
public class Bank implements Runnable{
private Account account;
public Bank(Account account) {
this.account = account;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
account.subtractAmount(1000);
System.out.println((i+1)+" sub : "+account.getBalance());
}
}
}
class Company implements Runnable{
private Account account;
public Company(Account account) {
this.account = account;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
account.addAmount(1000);
System.out.println((i+1)+" add : "+account.getBalance());
}
}
}
public class Main {
public static void main(String[] args) {
Account account = new Account();
account.setBalance(1000);
Company company = new Company(account);
Thread thread = new Thread(company);
Bank bank = new Bank(account);
Thread thread2 = new Thread(bank);
System.out.println("Account : Initial Balance: "+account.getBalance());
thread.start();
thread2.start();
try {
thread.join();
thread2.join();
System.out.println("Account : Final Balance: "+account.getBalance());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
synchronized关键字会降低应用程序的性能,因此只能在并发情景中需要修改共享数据的方法上使用它。当线程访问一个对象的同步方法时,他还可以调用这个对象的其他的同步方法,也包含正在执行的方法,而不必再次去获取这个方法的访问权。我们可以通过synchronized关键字来保护代码块(而不是整个方法)的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获得更好的性能。临界区(即同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。当这样使用synchronized关键字时,必须把对象引用作为传入参数。同一时间只有一个线程被允许访问这个synchronized代码。通常来说,我们使用this关键字来引用正在执行的方法所属的对象。
synchronized(this) {
}
2.使用非依赖属性实现同步
当使用synchronized关键字来保护代码块时,必须把对象引用作为传入参数。通常情况下,使用this关键字来引用执行方法所属的对象,也可以使用其他的对象对其进行引用。
public class Cinema {
private long vacanciesCinema1;
private long vacanciesCinema2;
private final Object controlCinema1,controlCinema2;
public Cinema() {
this.vacanciesCinema1 = 20;
this.vacanciesCinema2 = 20;
this.controlCinema1 = new Object();
this.controlCinema2 = new Object();
}
public boolean sellTickets1(int number) {
synchronized (controlCinema1) {
if (number<vacanciesCinema1) {
vacanciesCinema1 -= number;
return true;
}else {
return false;
}
}
}
public boolean sellTicket2(int number) {
synchronized (controlCinema2) {
if (number<vacanciesCinema2) {
vacanciesCinema2 -= number;
return true;
}else {
return false;
}
}
}
public boolean returnTickets1(int number) {
synchronized (controlCinema1) {
vacanciesCinema1 += number;
return true;
}
}
public boolean returnTicket2(int number) {
synchronized (controlCinema2) {
vacanciesCinema2 += number;
return true;
}
}
public long getTickets1() {
return vacanciesCinema1;
}
public long getTickets2() {
return vacanciesCinema2;
}
}
public class TicketOffice1 implements Runnable{
private Cinema cinema;
public TicketOffice1(Cinema cinema) {
this.cinema = cinema;
}
@Override
public void run() {
// TODO Auto-generated method stub
cinema.sellTickets1(3);
cinema.sellTickets1(2);
cinema.sellTicket2(2);
cinema.returnTickets1(3);
cinema.sellTickets1(5);
cinema.sellTicket2(2);
cinema.sellTicket2(2);
cinema.sellTicket2(2);
}
}
public class TicketOffice2 implements Runnable{
private Cinema cinema;
public TicketOffice2(Cinema cinema) {
this.cinema = cinema;
}
@Override
public void run() {
// TODO Auto-generated method stub
cinema.sellTicket2(2);
cinema.sellTicket2(4);
cinema.sellTickets1(2);
cinema.sellTickets1(1);
cinema.returnTicket2(2);
cinema.sellTickets1(3);
cinema.sellTicket2(2);
cinema.sellTickets1(2);
}
}
public class Main {
public static void main(String[] args) {
Cinema cinema = new Cinema();
TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
Thread thread1 = new Thread(ticketOffice1);
Thread thread2 = new Thread(ticketOffice2);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("Room 1 Vacancies: "+cinema.getTickets1());
System.out.println("Room 2 Vacancies: "+cinema.getTickets2());
}
}
这个例子使用了一个对象来控制对vacanciesCinema1属性的访问,所以同一时间只有一个线程能够修改这个属性;使用了另一个对象来控制vacanciesCinema2属性的访问,所以同一时间只有一个线程能够修改这个属性。但是,这个例子允许同时运行两个线程:一个修改vacanciesCinema1属性,另一个修改vacanciesCinema2属性。3.在同步代码中使用条件
在并发编程中一个典型的问题是生产者-消费者问题。对于这些场景,Java在Object类中提供了wait()、notify()和notifyAll()方法。线程可以在同步代码块中调用wait()方法,JVM将这个线程置入休眠,并且释放控制这个同步代码块的对象,同时允许其他线程执行这个对象控制的其他同步代码块。为了唤醒这个线程,必须在这个对象控制的某个同步代码块中调用notify()或者notifyAll()方法。
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
public class EventStorage {
private int maxSize;
private List<Date> storage;
public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
}
public synchronized void set() {
while(storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("Set : "+storage.size());
notifyAll();
}
public synchronized void get() {
while(storage.size()==0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Get : "+storage.size()+" : "+((LinkedList<?>)storage).poll());
notifyAll();
}
}
public class Producer implements Runnable{
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
storage.set();
}
}
}
public class Consumer implements Runnable{
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 100; i++) {
storage.get();
}
}
}
public class Main {
public static void main(String[] args) {
EventStorage storage = new EventStorage();
Producer producer = new Producer(storage);
Consumer consumer = new Consumer(storage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
4.使用锁实现同步
Java提供了同步代码块的另一种机制,它是一种比synchronized关键字更强大也更灵活的机制。这种机制基于Lock接口及其实现类(例如ReentrantLock),提供了更多的好处。
- 支持更灵活的同步代码块结构。使用synchronized关键字的获取和释放控制只能在同一个synchronized块结构中。Lock接口允许实现更复杂的临界区结构
- Lock接口提供了更多的功能。其中一个新功能是tryLock()的实现。这个方法试图获取锁,如果锁已被其他线程获取,它将返回false并继续往下执行代码
- Lock接口允许分离读和写操作,允许多个读线程和只有一个写线程
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class PrintQueue {
private final Lock queueLock = new ReentrantLock();
public void printJob(Object object) {
queueLock.lock();
try {
long duration = (long)(Math.random()*10000);
System.out.println(Thread.currentThread().getName()+":PrintQueue: Printing a Job during "+(duration/1000)+" seconds");
Thread.sleep(duration);
} catch (InterruptedException e) {
// TODO: handle exception
}finally {
queueLock.unlock();
}
}
}
public class Job implements Runnable{
private PrintQueue printQueue;
public Job(PrintQueue printQueue) {
this.printQueue = printQueue;
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Thread.currentThread().getName()+": Going to print a document");
printQueue.printJob(new Object());
System.out.println(Thread.currentThread().getName()+": The document has been printed");
}
}
public class Main {
public static void main(String[] args) {
PrintQueue printQueue = new PrintQueue();
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new Job(printQueue),"Thread "+i);
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
}
}
5.使用读写锁实现同步数据访问
锁机制最大的改进之一就是ReadWriteLock接口和他的唯一实现类ReentrantReadWriteLock。这个类有两个锁,一个是读操作锁,另一个是写操作锁。使用读操作锁时可以允许多个线程同时访问,但是使用写操作锁时只允许一个线程运行。在一个线程执行写操作时,其他的线程不能够执行读操作。
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class PricesInfo {
private double price1;
private double price2;
private ReadWriteLock lock;
public PricesInfo() {
price1 = 1.0;
price2 = 2.0;
lock = new ReentrantReadWriteLock();
}
public double getPrice1() {
lock.readLock().lock();
double value = price1;
lock.readLock().unlock();
return value;
}
public double getPrice2() {
lock.readLock().lock();
double value = price2;
lock.readLock().unlock();
return value;
}
public void setPrices(double price1,double price2) {
lock.writeLock().lock();
this.price1 = price1;
this.price2 = price2;
lock.writeLock().unlock();
}
}
public class Reader implements Runnable{
private PricesInfo pricesInfo;
public Reader(PricesInfo pricesInfo) {
this.pricesInfo = pricesInfo;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+": Price1: "+pricesInfo.getPrice1() );
System.out.println(Thread.currentThread().getName()+": Price2: "+pricesInfo.getPrice2());
}
}
}
public class Writer implements Runnable{
private PricesInfo pricesInfo;
public Writer(PricesInfo pricesInfo) {
this.pricesInfo = pricesInfo;
}
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 3; i++) {
System.out.println("Writer: Attempt to modify the prices");
pricesInfo.setPrices(Math.random()*10,Math.random()*8);
System.out.println("Writer: Prices have been modified");
try {
Thread.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
public class Main {
public static void main(String[] args) {
PricesInfo pricesInfo = new PricesInfo();
Reader[] readers = new Reader[5];
Thread[] threads = new Thread[5];
for (int i = 0; i < 5; i++) {
readers[i] = new Reader(pricesInfo);
threads[i] = new Thread(readers[i]);
}
Writer writer = new Writer(pricesInfo);
Thread thread = new Thread(writer);
for (int i = 0; i < 5; i++) {
threads[i].start();
}
thread.start();
}
}