1. Java内存模型:
java的对象都是在主内存(物理内存)中创建的;而各个线程在执行的时候,为了隔离相互之间的影响,以及降低CPU和内存IO之间速率相差太大的影响,会将主内存中的变量读取到工作内存中(高速缓存,寄存器),然后在工作内存中操作,完成之后再写回到主内存中。
主内存和工作内存中的操作由以下几部分操作组成,每部分均是原子性的:
首先通过read、load将变量的值放入工作内存中;当执行引擎需要使用变量时,使用use操作,读取工作内存的变量,操作完之后使用assign,将新的变量值写回工作内存;当需要将变量值同步回主内存时,通过store、write操作将工作内存的值刷新到主内存中。
java虚拟机规定read和load,store和write必须成对出现。
每次lock一个变量,将会清空该变量在工作内存中的值。
2. 并发编程的3个特性
2.1 原子性:
- 基本变量的读取和赋值是原子性的,结合上面的操作顺序,即可得知,因为每次read和write均是原子性的。但是java对于64位的变量,允许拆分成两部分的32位分别操作,因此对于double和long的读写,可能没有原子性。但实际上大多数jvm在具体实现时,也都将这两种类型的读写实现为原子性了。
- synchronized。必须先lock才能进入同步代码块,因此一个线程在unlock之前,其他线程无法进入同步代码块,保证了同步代码块的原子性。
2.2 可见性
- volatile。被volatile修饰的变量,在每次use之前,都需要先read和load,因此保证了每次读取的都是主内存的最新值;而在每次assign的时候,都需要store和write回主内存,因此保证了最新值可以刷新到主内存。
- synchronized。在同步代码块中,每次写变量,都会先lock住变量,然后清空工作内存中共享变量的值;写完变量之后,都会先将工作内存中的共享变量刷新到主内存中,然后unlock(关于synchronized的可见性,可以见:synchronized可见性)。
2.3 有序性
- volatile。JVM规定,在volatile之前的操作,不能重排序到volatile之后。
- synchronized。同步代码块对于不同线程来说串行进入的。
3. 一段同步代码的分析
import java.util.Date;
import java.util.concurrent.TimeUnit;
public class ThreadTest extends Thread {
private static boolean stop;
// private static volatile boolean stop;
synchronized void f() {}
@Override
public void run() {
System.out.println("start");
while (!stop) {
// f();
// System.out.println("loop");
}
System.out.println("end");
}
public static void main(String[] args) {
try {
ThreadTest t = new ThreadTest();
t.start();
Thread.sleep(2000);
t.stop = true;
System.out.println("stop");
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println(e.getMessage());
}
}
}
由于stop变量的不可见性的,所以线程并不会在stop设为true之后停止,原因是stop不具有可见性,但是如果用volatile修饰stop,或者在循环中调用synchronized方法,则可以使线程停下来;另外循环中调用控制台输出,也可以停下线程,猜测是输出到控制台的时候调用了synchronized方法。
这段类似的代码在《Effective Java》中提到,编译器在优化的时候也可能优化为:
while(!stop){
while(true){
// do something
}
}
4. 双重锁单例模式的一个细节分析
private static volatile TestSingleton instance = null;
public static TestSingleton getInstance() {
if (instance == null) {
synchronized (TestSingleton.class) {
if (singleton == null) {
singleton = new TestSingleton();
}
}
}
return instance;
}
上面是一个典型的单例实现方式,关于volatile的作用,大部分地方会解释为可见性,但是此处使用synchronized已经保证了singleton的可见性,为何还要使用volatile。其实这里更多的是考虑有序性,防止代码重排序,如果在创建TestSingleton的时候,先将TestSingleton对象的内存地址返回给singleton了,而TestSingleton还没有完成初始化,此时,其他线程可能拿到一个不完整的singleton。具体可见:单例模式的实现,在这篇博文的评论中有说到。
参考资料:
- 《深入理解Java虚拟机》
- 《Effective Java》
- http://blog.csdn.net/jason0539/article/details/23297037
- http://www.imooc.com/video/6780