Volatile

volatile是什么?

volatile是JVM提供的轻量级的同步机制。

  1. 保证可见性;
  2. 不保证原子性;
  3. 禁止指令重排;

JMM

(Java内存模型)

要求是:原子性,有序性,可见性。

本身是不存在的,它描述的是一组规则或规范,通过这组规范定义了程序中各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。

可见性

JMM关于同步的规定:

  1. 线程解锁前,必须将共享变量的值刷新回主内存
  2. 线程加锁前,必须读取主内存的值到自己的工作内存
  3. 加锁解锁是同一把锁

        由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈空间),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先将变量从主内存拷贝的自己的工作内存空间,然后对变量进行操作,操作完成后,再将变量写回主内存。不能直接操作主内存中的变量。各个线程中的工作内存中存储着主内存中的变量副本拷贝。因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成。其简要的过程如下图:

 如下示例:说明线程的可见性

在公共区放置对象。他的属性age=25。所有线程先拿到25,然后在自己的线程工作空间中对数据进行操作,在返回给主内存。

主物理内存发生修改,则迅速通知其他线程,这就叫做可见性。(主内存就是插在电脑上的内存)

代码展示

验证volatile的可见性

class MyData{
int number = 0;
public void  add{
this.number+=60;
}
public static void main (String[] args){
public void seeOkVolatile(){
MyData mydata = new MyData();
new Thread(()->{
system.out.println(Thread.currentThread().getName()+"come in");
try{
TimeUntil.SECOND.sleep(3);
}catch(Exception e){
e.printStackTrace();
}
mydata.add();
system.out.println(Thread.currentThread().getName()+"come out");
},
"AAA").start();
while(mydata.number==0){
system.out.println("main 阻塞");
}

}
}

 改变之后(给变量添了volatile关键字)

class MyData{
    volatile int number = 0;
public void  add{
    this.number+=60;
}
    public static void main (String[] args){
        public void seeOkVolatile(){
        MyData mydata = new MyData();
            new Thread(()->{
    system.out.println(Thread.currentThread().getName()+"come in");
    try{
        TimeUntil.SECOND.sleep(3);
       }catch(Exception e){
            e.printStackTrace();
        }
        mydata.add();
        system.out.println(Thread.currentThread().getName()+"come out");
        },"AAA").start();
    while(mydata.number==0){
        system.out.println("main 阻塞");
     }

    }
}

在加了volatile关键字之后,对于每一个线程对共享变量的修改都会通知主内存的, 然后,这样就体现了可见性。

volatile不保证原子性

原子性是什么?

不可分割,中间不能被加塞或者分割。要么同时成功,要么同时失败。

如下表示有20个线程,

for(int i = 0;i<20;i++){
      new Thread(()->{

            },String.valueOf(i)+"name").start();

 本应输出number是20000,但是每次都不会达到这个值,所以不保证原子性。

这是加了Synchronize来解决++的多线程不安全问题。

理论上是可以用synchronize的额,但是不值得。

不保证原子性的理论知识

线程调度太快了,后面的额线程会把前面的线程的写回给主内存的值覆盖掉。所以不保证原子性。

解决不保证原子性

  1. 加synchronize,
  2. 使用JUC下的Atomic类

运行结果对比:

有序性

但是在volatile关键字修饰下是:禁止指令重排序

案例

在执行的时候通过指令重排,flag的执行在a前面。执行完后,未来的及执行a,直接走method02,a的值是从0加5的,而不是从1开始加5的。导致了不确定的结果,这是我们不期望的。

禁止指令重排小结

禁止指令重排,从而避免了多线程环境下程序出现乱序执行现象。

你在哪些地方用到过volatile?(经典面试)

单例模式中

在没加volatile,synchronized之前的单例模式:

public class SingleDemo{
    private static  SingleDemo instance=null;
    private void SingleDemo(){
        system.out.println("构造器");
    }
    public static SingleDemo getInstance(){
      if(instance==null){
         instance = new SingleDemo();
       }
      return instance;
    }
    public static void main(String[] args){
        for(int i = 0;i<10;i++){
            new Thread(()->{
                SingleDemo.getInstance();
            },String.valueOf(i)).start();
         }

    }
}

 运行如下:

 这种问题可以通过在getInstance函数前面加synchronized 解决,但是非常不推荐。

利用双端检锁解决:

在进去之前判断一次,进去后上锁,出来时再判断一次。但是,仍然是不安全的。因为底层有指令重排。

public class SingleDemo{
    private static  SingleDemo instance=null;
    private void SingleDemo(){
        system.out.println("构造器");
    }

//双端检锁机制
    public static SingleDemo getInstance(){
      if(instance==null){
           synchronized(SingleDemo.class){
                 instance = new SingleDemo();
           }
        }
      return instance;
    }
    public static void main(String[] args){
        for(int i = 0;i<10;i++){
            new Thread(()->{
                SingleDemo.getInstance();
            },String.valueOf(i)).start();
         }

    }
}

 

 所以当instance不为null时,由于instance为初始化完成,这样也会造成多线程安全问题。

我们在申明的属性前加上volatile关键字,就可以解决。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值