本质
Java多线程安全问题的本质原因,有线程的写操作。如果多个线程对同一资源进行读,是没有问题的。但如果一个线程写到一半,其他线程读,就会出问题。因此需保证写的操作完成后才能读。
以下内容精简自:http://tutorials.jenkov.com/java-concurrency/index.html
Java Memory Model
The Internal Java Memory Model
Thread stack又可以称为Call stack。在某线程中“call”的方法中的Local variables都会保存在Thread stack中,这里的Local variables包括primitive type和object reference。
不管在哪个Thread中new的Object都保存在Heap中,Object的member variables保存在Heap中,但是Object的method中存在的Local variables依然保存在Thread Stack中。
Static class variables也在Heap中。
Hardware Memory Architecture
一个modern computer hardware architecture会对存储进行分层缓存,本质是因为访问速度的不同。CPU进行读写操作的时候会有从底层向上read从顶层向下flush的过程。
Thread stack和Heap都是存在于Main memory的,但是Thread stack和Heap又可能出现在CPU caches或CPU registers中.
这种不确定性会产生两个主要的问题:
- Thread updates (writes) shared variables的可见性(Visibility)
- 不同线程读写shared variables时产生的竞争状态(Race conditions)
Visibility of Shared Objects
多个线程共享一个Object的情况下,没恰当使用volatile
和synchronization的话,一个线程对shared object的改变可能对其他线程不可见。
可以使用volatile
解决上述问题。
volatile
volatile是易变的意思。用来标记一个Java variable存储在 main memory中。
Full volatile Visibility Guarantee
Java volatile
关键字的可变性保证是超过 volatile
修饰的变量本身的。详细的读这里。
因为JVM会Instruction Reordering,volatile关键字还提供“happens-before”保证。
对其他变量的读写不能reorder到对volatile变量的写之后。位于volatile的写后面的对其他变量的读写还是可能reorder到前面的。也就是From after to before is allowed, but from before to after is not allowed.
对其他变量的读写不能reorder到对volatile变量的读之前。也就是From before to after is allowed, but from after to before is not allowed.
volatile is Not Always Enough
它的使用场景:
只有一个线程读写某volatile变量,其他线程只读此变量,可以保证线程读到的是最新的值。
如果两个线程都对shared variable进行读写,使用volatile是不够的!
Race Conditions
两个线程同时读取同一个Object再进行修改的话,会出现Race Conditions的问题。
可以用synchronized block解决竞争机制,synchronized blocks保证其中的所有变量会从Main memory中读取,并且当 Thread 退出synchronized block时, 所有更新后的变量会flushed回Main memory,不管变量是否是volatile的。