基本概念
Java 内存模型中的可见性、原子性和有序性。
- 可见性:是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。
在java中,volatile
、synchronized
和final
实现可见性。 - 原子性: 一个操作是不可中断的,要么全部执行成功要么全部执行失败。
在java中,synchronized
和在lock
、unlock
中操作保证原子性,volatile
是非原子性的。 - 有序性: 所有的操作都是有顺序的。volatile 是因为其本身包含“禁止指令重排序”的语义,synchronized 是由“一个变量在同一个时刻只允许一条线程对其进行 lock 操作”这条规则获得的,此规则决定了持有同一个对象锁的两个同步块只能串行执行。
在java中,synchronized
和volatile
均具有有序性。
Volatile原理
volatile变量是用来确保将变量的更新操作通知到其他线程。当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。
在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
当对非 volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的 CPU cache 中。
而声明变量是 volatile 的,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步。
volatile 性能:
volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行。
Volatile适用场景
-
适用于对变量的写操作不依赖于当前值,对变量的读取操作不依赖于非volatile变量。
独立观察模式
安全使用 volatile 的另一种简单模式是:定期 “发布” 观察结果供程序内部使用。【例如】假设有一种环境传感器能够感觉环境温度。一个后台线程可能会每隔几秒读取一次该传感器,并更新包含当前文档的 volatile 变量。然后,其他线程可以读取这个变量,从而随时能够看到最新的温度值。 -
适用于读多写少的场景。
- 可用作状态标志。
一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。
volatile boolean shutdownRequested;
...
public void shutdown() {
shutdownRequested = true;
}
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
线程1执行doWork()的过程中,可能有另外的线程2调用了shutdown,所以boolean变量必须是volatile。
- 解决经典的双重检查锁定的线程安全问题
单例的完美写法
//注意volatile!!!!!!!!!!!!!!!!!
private volatile static Singleton instace;
public static Singleton getInstance(){
//第一次null检查
if(instance == null){
synchronized(Singleton.class) { //1
//第二次null检查
if(instance == null){ //2
instance = new Singleton(); //3
}
}
}
return instance;
如果不用volatile,则因为内存模型允许所谓的“无序写入”,可能导致失败。——某个线程可能会获得一个未完全初始化的实例。
- JDK中volatie应用:JDK中ConcurrentHashMap的Entry的value和next被声明为volatile,AtomicLong中的value被声明为volatile。AtomicLong通过CAS原理(也可以理解为乐观锁)保证了原子性。
提醒
volatile不能用于安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个(读取-修改-写入)操作序列组成的组合操作,必须以原子方式执行,而volatile不能提供原子性。
网友评论