Java中线程同步可以通过wait、notify、notifyAll等方法实现。这几个方法在最顶级的父类Object中实现,并且被声明为final,所以子类无法重写这几个方法。在实现线程同步时,一般需要配合synchronized关键字使用,定义同步代码块或者方法。JDK 1.5以后提供了Condition来实现线程间的协作,Condition提供的await、signal、signalAll方法相对于wait、notify、notifyAll的方法更加安全高效,Condition所使用的是ReentrantLock锁。
1 synchronized关键字和ReentrantLock类
理解synchronized关键字必须首先了解下Java的内存模型。
Java中每一个进程都有自己的主内存,进程中的每个线程有自己的线程内存,线程从主内存中获取数据在线程内存中计算完成后回写到主内存中。在并发情况下就可能造成数据过期数据的问题。具体例子看如下代码:
public class TestSync {
public static int sum = 0;
public static class MyThreadA implements Runnable {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
sum++;
}
}
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) { //10个任务交给线程池, 返回的数据预期为10*10000
MyThreadA myThreadA = new MyThreadA();
executorService.execute(myThreadA);
}
executorService.shutdown();
System.out.println(sum);
}
}
执行结果如下:
88625
从执行结果可以看出,并不是预期中的100000。原因就在数据过期的问题。例如线程A和线程B同时从主内存中获取sum的值为1500。线程A计算了1000次,此时线程A内存中的sum为2500,并向主内存回写sum=2500,后交出CPU;线程B获得CPU开始计算了900次,此时线程B内存中的sum=2400,并向主内存回写sum=2400,后交出CPU。此时主内存的sum=2400,而预期是1500+1000+900=3400。
使用synchronized关键字改进代码如下:
public class TestSync {
public static int sum = 0;
public static Object lock = new Object(); //自定义锁对象,代价较小
public static class