Java volatile:修订间差异
第108行: | 第108行: | ||
* [http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html JSR-133 FAQ] | * [http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html JSR-133 FAQ] | ||
* [http://gee.cs.oswego.edu/dl/jmm/cookbook.html JMM cookbook] | * [http://gee.cs.oswego.edu/dl/jmm/cookbook.html JMM cookbook] | ||
* [http://gee.cs.oswego.edu/dl/html/j9mm.html Using JDK 9 Memory Order Modes] | |||
[[Category:Concurrency]] | [[Category:Concurrency]] |
2021年5月1日 (六) 14:06的版本
Java内存模型
JSR-133(Java 5)中定义了Java的内存模型。
指令重排
For example, if a thread writes to field a and then to field b, and the value of b does not depend on the value of a, then the compiler is free to reorder these operations, and the cache is free to flush b to main memory before a. There are a number of potential sources of reordering, such as the compiler, the JIT, and the cache.
一个例子:
- r1 和 r2都是局部变量,A和B是共享变量
- 初始化
A == B == 0
Thread 1 Thread 2 1: r2 = A; 3: r1 = B; 2: B = 1; 4: A = 2;
按照如上的代码运行,理论上不应该出现 r2 == 2, r1 == 1的情形。因为r2如果为2说明线程2先执行,这时候B还是为0,所以r1应该为0。
但是实际上编译器在不改变单线程执行的语义的情况下,是可以对指令进行重新排序的,如:
Thread 1 Thread 2 B = 1; r1 = B; r2 = A; A = 2;
因此会导致上述问题(forward substitution)。另一个例子(p==q,p.x=0):
Thread 1 Thread 2 r1 = p; r6 = p; r2 = r1.x; r6.x = 3; r3 = q; r4 = r3.x; r5 = r1.x;
这里编译器可能会复用r2读取到的值给r5。
happen before
If one action happens-before another, then the first is visible to and ordered before the second.
- An unlock on a monitor happens before every subsequent lock on that same monitor.
- A write to a volatile field happens before every subsequent read of that same volatile.
- A call to start() on a thread happens before any actions in the started thread.
- All actions in a thread happen before any other thread successfully returns from a join() on that thread.
- The default initialization of any object happens-before any other actions (other than default-writes) of a program.
volatile
volatile实现
线程变量存在于公共堆栈和私有堆栈中,当JVM以-server模式启动时,为了提高线程运行时效率,线程一直在私有堆栈中取值。设置成volatile后,则会强制从公共堆栈中取值。使用volatile关键字增加了实例变量在多个线程之间的可见性。
volatile不能保证原子性。
- 对于 volatile的变量,java保证每次都是从主存中读取(而不是线程的局部变量中)
- 其读取都是原子的(包括long和double)
volatile只保证可见性,但是JVM规范中没有提及其是否会禁止指令重排!
volatile使用场景
典型的应用是利用volatile变量控制循环退出。一般使用时应该满足如下的所有的原则:
- 对变量对写入操作不依赖于变量的当前值,或者保证只有一个线程更新变量的值
- 该变量不会与其他状态变量一起纳入不变性条件中
- 访问变量时不需要加锁
synchronized
- mutual exclusion: only one thread can hold a monitor at once, so synchronizing on a monitor means that once one thread enters a synchronized block protected by a monitor, no other thread can enter a block protected by that monitor until the first thread exits the synchronized block.
- ensures that memory writes by a thread before or during a synchronized block are made visible in a predictable manner to other threads which synchronize on the same monitor.
- After we exit a synchronized block, we release the monitor, which has the effect of flushing the cache to main memory, so that writes made by this thread can be visible to other threads.
- Before we can enter a synchronized block, we acquire the monitor, which has the effect of invalidating the local processor cache so that variables will be reloaded from main memory. We will then be able to see all of the writes made visible by the previous release.
volatile和synchronized比较
- volatile是线程同步的轻量级实现,性能稍优于synchronized,volatile只能修饰变量。
- 多线程访问volatile不会导致阻塞,但synchronized会出现阻塞
- volatile能保证数据的可见性,但不能保证原子性;而synchronized可以保证原子性,间接保证了可见性
- volatile解决变量在多个线程之间的可见性,而synchronized解决的是多个线程之间访问资源的同步性。
- synchronized可以保证互斥性和可见性,保证进入同步方法或者代码块的每个线程都看到由同一个锁保护之前所有的修改效果。
Under the new memory model, it is still true that volatile variables cannot be reordered with each other. The difference is that it is now no longer so easy to reorder normal field accesses around them.
Writing to a volatile field has the same memory effect as a monitor release, and reading from a volatile field has the same memory effect as a monitor acquire.
In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.
class VolatileExample {
int x = 0;
volatile boolean v = false;
public void writer() {
x = 42;
v = true;
}
public void reader() {
if (v == true) {
//uses x - guaranteed to see 42.
}
}
}
see: