前言
Java语言提供了一种稍弱的同步机制,即volatile变量,用来确保将变量的更新操作通知到其他线程。volatile 变量具备两种特性,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile类型的变量时总会返回最新写入的值。volatile关键字的作用(变量可见性、禁止重排序)
变量可见性:
其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。
禁止重排序:
volatile 禁止了指令重排。
一、实现机制
- 在访问volatile变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此volatile变量是一种比sychronized关键字更轻量级的同步机制。
- volatile适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。
- 当对非volatile 变量进行读写的时候,每个线程先从内存拷贝变量到CPU缓存中。如果计算机有多个CPU,每个线程可能在不同的CPU上被处理,这意味着每个线程可以拷贝到不同的CPU cache 中;
- 当声明变量是volatile 的,JVM 保证了每次读变量都从内存中读,跳过CPU cache 这一步。
- 因为jvm的操作不都是原子操作,如i++,++i等,所以volatile修饰的变量对于非原子操作并不能保证其线程安装。
二、适用范围
(1)对变量的写操作不依赖于当前值(比如i++),或者说是单纯的变量赋值(boolean flag = true)。
(2)该变量没有包含在具有其他变量的不变式中,也就是说,不同的volatile变量之间,不能互相依赖。只有在状态真正独立于程序内其他内容时才能使用volatile。
三、实例应用
3.1、volatile替换sychronized
public class StopThread {
private static Boolean stopRequested;
private static synchronized void requestStop(){
stopRequested=true;
}
private static synchronized Boolean stopRequested(){
return stopRequested;
}
public static void main(String[]args)throws InterruptedException{
Thread backgroundThread=new Thread(()->{
int i=0;
while(!stopRequested()){
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
requestStop();
}
}
使用volatile关键字优化
public class StopThread {
private static volatile Boolean stopRequested;
public static void main(String[]args) throws InterruptedException{
Thread backgroundThread = new Thread(()->{
int i=0;
while(!stopRequested){
i++;
}
});
backgroundThread.start();
TimeUnit.SECONDS.sleep(1);
stopRequested=true;
}
}
3.2、volatile的非原子操作
错误的应用
private static volatile int nextSerialNumber=0;
public static int generateSerialNumber(){
return nextSerialNumber++;
}
正确的应用
private static final AtomicLong nextSerialNum = new AtomicLong();
public static long generateSerialNumber(){
return nextSerialNum.getAndIncrement();
}