volatile——学习笔记

一、概念

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

二、特性

1.保证可见性

volatile很好的保证了变量的可见性,变量经过volatile修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,加了这个指令后,会引发两件事情:

  1. 将当前处理器缓存行的数据写回到系统内存
  2. 这个写回内存的操作会使得在其他处理器缓存了该内存地址无效(多处理器核心的情况下)

什么意思呢?意思就是说当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,它会去内存中读取新值,这就保证了可见性。

2.不保证原子性

2.1 原子性是啥?

不可分割,完整性,要么同时成功,要么同时失败

2.2 代码Demo
public class Volatiletest {
    static  volatile   int num = 0 ;
    static  void  plus()
    {
        num++;
    }
    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i <200 ; i++) {
            new Thread(()->{
                for (int j = 0; j <500 ; j++) {
                    plus();
                }
            }).start();
        }
        while (Thread.activeCount()>2)
        {
            Thread.yield();
        }
        System.out.println("num期望值是100000,实际值"+num);
    }
}


在这里插入图片描述

原因是num++这条程序,底层的字节码有三步骤
在这里插入图片描述
分别是,从内存中获得值,计算值,存值入内存
如果,A线程获得了num的值,还没有来得及计算和将值更新到内存,B线程又读到了num的值,那么此时A,B线程读到的num值是一样的,他们执行计算操作后存入内存,实际上执行了两次计算,但从内存的num值来看,只计算了一次

2.3.解决方法

使用原子操作数类(通过CAS自旋确保每次得到的都是最新的值,只有值是最新的情况下才能更新值)

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

3.禁止指令重排

(PS 当一组指令不存在数据依赖,且单线程下执行顺序不影响执行结果,JVM判断这组指令可以重排)
来自于<<java并发编程的艺术>>
来自于<<java并发编程的艺术>>
由表可以看出,当第二个操作是volatile写时,无论前一个操作是什么,都不能重排序
当第一个操作是volatile读,不管第二个操作是什么,都不能重排序

编译器在指令序列中插入内存屏障来禁止重排序,
以下是JMM内存屏障插入策略

  1. 在每个volatile写之前插入StoreStore (禁止屏障上面的volatile写和屏障下面的volatile写重排序
  2. 在每个volatile写之后插入StoreLoad(禁止屏障上面的volatile写和屏障下面的volatile读重排序
  3. 在每个volatile读之后插入一个LoadLoad(禁止屏障上面的volatile读和屏障下面的volatile读重排序)
    和LoadStore(禁止屏障上的volatile读和屏障下面的volatile写重排序)

三、拓展,单例模式与volatile

首先我们知道一个双重检查模式的单例模式(懒汉式)如下


public class SingleDemo {

    private static SingleDemo singleDemo;
    private SingleDemo()
    {
        System.out.println("单例模式构造函数");
    }
    //双重检查,也不不一定线程安全,存在指令重排
    static SingleDemo getInstance()
    {
        if (singleDemo ==null) {
            synchronized (SingleDemo.class)
            {
                if (singleDemo ==null)
                    singleDemo = new SingleDemo();
            }
        }
        return singleDemo;
    }
 }

在双重检查的条件下,也不是绝对线程安全的
因为
singleDemo = new SingleDemo();
执行了三条指令
1.分配内存空间
2.初始化对象
3.将内存空间赋值给引用
而步骤2和3不存在数据依赖关系且单线程下执行顺序不影响程序执行结果,所以编译器判断这个两条指令是可以重新排序的。这就可能造成如下结果:
内存空间赋值给引用后,引用不为null,但对象的初始化还没有完成,其他线程再访问此单例,以为它已经存在,其他它还不存在
(你以为我在第三层,其实我还在第二次爬着)
解决方法:将单例引用设为volatile型的,禁止指令的重排序

private static volatile SingleDemo singleDemo;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值