【70期】面试官:对并发熟悉吗?谈谈对volatile的使用及其原理
共 5908字,需浏览 12分钟
·
2020-10-25 10:21
阅读本文大概需要 8 分钟。
来自:www.cnblogs.com/paddix/p/5428507.html
一、volatile的作用
二、volatile的使用
1、防止重排序
package com.paddx.test.concurrent;
public class Singleton {
public static volatile Singleton singleton;
/**
* 构造函数私有,禁止外部实例化
*/
private Singleton() {};
public static Singleton getInstance() {
if (singleton == null) {
synchronized (singleton) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
分配内存空间。
初始化对象。
将内存空间的地址赋值给对应的引用。
分配内存空间。
将内存空间的地址赋值给对应的引用。
初始化对象
2、实现可见性
package com.paddx.test.concurrent;
public class VolatileTest {
int a = 1;
int b = 2;
public void change(){
a = 3;
b = a;
}
public void print(){
System.out.println("b="+b+";a="+a);
}
public static void main(String[] args) {
while (true){
final VolatileTest test = new VolatileTest();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.change();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
test.print();
}
}).start();
}
}
}
......
b=2;a=1
b=2;a=1
b=3;a=3
b=3;a=3
b=3;a=1
b=3;a=3
b=2;a=1
b=3;a=3
b=3;a=3
......
3、保证原子性
17.7 Non-Atomic Treatment of double and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency's sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.
Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
package com.paddx.test.concurrent;
public class VolatileTest01 {
volatile int i;
public void addI(){
i++;
}
public static void main(String[] args) throws InterruptedException {
final VolatileTest01 test01 = new VolatileTest01();
for (int n = 0; n < 1000; n++) {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
test01.addI();
}
}).start();
}
Thread.sleep(10000);//等待10秒,保证上面程序执行完成
System.out.println(test01.i);
}
}
读取i的值。
对i加1。
将i的值写回内存。
注:上面几段代码中多处执行了Thread.sleep()方法,目的是为了增加并发问题的产生几率,无其他作用。
三、volatile的原理
1、可见性实现:
修改volatile变量时会强制将修改后的值刷新的主内存中。
修改volatile变量后会导致其他线程工作内存中对应的变量值失效。因此,再读取该变量值的时候就需要重新从读取主内存中的值。
2、有序性实现:
Two actions can be ordered by a happens-before relationship.If one action happens before another, then the first is visible to and ordered before the second.
Each action in a thread happens before every subsequent action in that thread.
An unlock on a monitor happens before every subsequent lock on that monitor.
A write to a volatile field happens before every subsequent read of that 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.
If an action a happens before an action b, and b happens before an action c, then a happens before c.
同一个线程中的,前面的操作 happen-before 后续的操作。(即单线程内按代码顺序执行。但是,在不影响在单线程环境执行结果的前提下,编译器和处理器可以进行重排序,这是合法的。换句话说,这一是规则无法保证编译重排和指令重排)。
监视器上的解锁操作 happen-before 其后续的加锁操作。(Synchronized 规则)
对volatile变量的写操作 happen-before 后续的读操作。(volatile 规则)
线程的start() 方法 happen-before 该线程所有的后续操作。(线程启动规则)
线程所有的操作 happen-before 其他线程在该线程上调用 join 返回成功后的操作。
如果 a happen-before b,b happen-before c,则a happen-before c(传递性)。
3、内存屏障
LoadLoad 屏障
执行顺序:Load1—>Loadload—>Load2
确保Load2及后续Load指令加载数据之前能访问到Load1加载的数据。StoreStore 屏障
执行顺序:Store1—>StoreStore—>Store2
确保Store2以及后续Store指令执行前,Store1操作的数据对其它处理器可见。LoadStore 屏障
执行顺序:Load1—>LoadStore—>Store2
确保Store2和后续Store指令执行前,可以访问到Load1加载的数据。StoreLoad 屏障
执行顺序: Store1—> StoreLoad—>Load2
确保Load2和后续的Load指令读取之前,Store1的数据对其他处理器是可见的。
package com.paddx.test.concurrent;
public class MemoryBarrier {
int a, b;
volatile int v, u;
void f() {
int i, j;
i = a;
j = b;
i = v;
//LoadLoad
j = u;
//LoadStore
a = i;
b = j;
//StoreStore
v = i;
//StoreStore
u = j;
//StoreLoad
i = u;
//LoadLoad
//LoadStore
j = b;
a = i;
}
}
四、总结
对变量的写操作不依赖于当前值。
该变量没有包含在具有其他变量的不变式中。
推荐阅读:
【69期】面试官:对并发熟悉吗?谈谈线程间的协作(wait/notify/sleep/yield/join)
【68期】面试官:对并发熟悉吗?说说Synchronized及实现原理
【67期】谈谈ConcurrentHashMap是如何保证线程安全的?
微信扫描二维码,关注我的公众号
朕已阅