volatile是什么
volatile:JVM提供的轻量级的同步机制
volatile的特点
可见性(及时通知其他线程主物理内存的值已经改变)
原子性无保证
有序性(禁止指令重排)
1.可见性
//模拟服务器,服务器运行接收到信号后stop
class Server{
private static volatile boolean isStop;
public static void stop(){ isStop=true;}
public static void run(){while(!isStop){}} //服务器运行
public static void main(String[] args) throws InterruptedException {
new Thread(Server::run,"t1").start();
TimeUnit.SECONDS.sleep(2);
new Thread(Server::stop,"t2").start();//服务器收到了stop信号
}
}
若变量isStop省略修饰符volatile,则程序无法停止
原因:线程t2修改isStop后其余线程不可见
注:volatile的实现依赖于CPU的缓存一致性协议
2.原子性
卖票时,所有线程同时拿到了同一数据,线程1修改后将数据写回主物理内存时被挂起,线程2将修改的数据写回主内存,并通知其他线程主物理内存中的值已改变,但线程1此时唤醒,在收到通知前将数据写回了主物理内存,出现了丢失写值(写覆盖)的现象
解决方案:
1>加同步锁
2>使用JUC下的原子操作(如:AtomicInteger等),可保证程序的原子性(原理是CAS+volatile)
class Main{
AtomicInteger num=new AtomicInteger();
CountDownLatch count=new CountDownLatch(20);
public void method() {
count.countDown();
for(int i=0;i<200;i++) num.getAndIncrement();
}
public static void main(String[] args) throws InterruptedException {
Main m=new Main();
for(int i=0;i<20;i++) { new Thread(m::method).start(); }
m.count.await();
System.out.println(m.num);
}
}
运行结果:4000,若num采用int类型,则结果<=4000
原因:num++不是原子性操作,但我们利用了JUC下的原子操作AtomicInteger
3.有序性
synchronized有序因为其只有单个线程在执行,发生指令重排一般也没有关系
volatile禁止指令重排保证了有序性
class Singleton{
private static volatile Singleton obj;
private Singleton(){};
public static Singleton getInstance(){
// B线程检测到obj不为空在此处返回变量尚未被初始化的对象
if(obj==null){
synchronized (Singleton.class){
if(obj==null){ obj=new Singleton(); }// A线程被指令重排,obj被赋值但其变量未被初始化
}
}
return obj;
}
}
注:在JVM中对象创建分三步,申请内存、成员变量初始化、将指针指向堆内存中对象,但2、3可能会发生指令重排序
指令重排在编译器认为这是一种优化,但若我们的程序在多线程中使得数据的一致性无法保证
禁止指令重排,即禁止指令重排优化,避免在多线程环境下出现执行乱序的情况
附加知识点:CAS
为什么Atomic可解决原子性?
解:底层是CAS,详情见CAS详解