大家好,我是"java继父"伯约,这篇对大家有帮助的话求一个赞,另外文章末尾放了我从月入7k到现在3W的学习资料,大家可以去领一下(无偿)。
1.防重排序
我们从一个最经典的例子来分析重排序问题。大家应该都很熟悉单例模式的实现,而在并发环境下的单例实现方式,我们通常可以采用双重检查加锁(DCL)的方式来实现。其源码如下:
public class Singleton {
public static volatile Singleton singleton;
/**
* 构造函数私有,禁止外部实例化
*/
private Singleton() {};
public static Singleton getInstance() {
if (singleton == null) {
synchronized (singleton.class) {
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
现在我们分析一下为什么要在变量singleton之间加上volatile关键字。要理解这个问题,先要了解对象的构造过程,实例化一个对象其实可以分为三个步骤:
- 分配内存空间。
- 初始化对象。
- 将内存空间的地址赋值给对应的引用。
但是由于操作系统可以对指令进行重排序,所以上面的过程也可能会变成如下过程:
- 分配内存空间。
- 将内存空间的地址赋值给对应的引用。
- 初始化对象
如果是这个流程,多线程环境下就可能将一个未初始化的对象引用暴露出来,从而导致不可预料的结果。因此,为了防止这个过程的重排序,我们需要将变量设置为volatile类型的变量。
2.实现可见性
可见性问题主要指一个线程修改了共享变量值,而另一个线程却看不到。引起可见性问题的主要原因是每个线程拥有自己的一个高速缓存区——线程工作内存。volatile关键字能有效的解决这个问题,我们看下下面的例子,就可以知道其作用:
public class Test {
private static boolean stop = false;
public static void main(String[] args) {
// Thread-A
new Thread("Thread AA") {
@Override
public void run() {
while (!stop) {
}
System.out.println(Thread.currentThread() + " stopped");
}
}.start();
// Thread-main
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread() + " after 1 seconds");
} catch (InterruptedException e) {
e.printStackTrace();
}
stop = true;
}
}
执行输出如下:
Thread[main,5,main] after 1 seconds
// Thread A一直在loop, 因为Thread A 由于可见性原因看不到Thread Main 已经修改stop的值
可以看到 Thread-main 休眠1秒之后,设置 stop = ture,但是Thread A根本没停下来,这就是可见性问题。如果通过在stop变量前面加上volatile关键字则会真正stop:
Thread[main,5,main] after 1 seconds
Thread[Thread A,5,main] stopped
Process finished with exit code 0
3.保证原子性:单次读/写
volatile不能保证完全的原子性,只能保证单次的读/写操作具有原子性。
4.volatile能保证原子性吗?
不能完全保证,只能保证单次的读/写操作具有原子性。