线程并发艺术之二:volatile关键字

定义:

java语言规范第三版:

java编程计语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排它锁单独获取这个变量。

java提供了volatile关键字,在某些情况下比锁更加方便。如果一个字段(变量)被volatile修饰,java线程内存模型确保所有线程看到这个变量值是一样的。

 

   在x86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,cpu会做什么事情。

java代码:

instance =new Singleton();//instance是volatile变量

 

对应的汇编代码:

0x01a3de1d:movb $x0,0x1104800(%esi);

0x0xa3de24: lock add1 $0x0,(%esp);

Lock前缀的指令在多核处理器下会引发两件事:

  • 将当前处理器缓存行的数据写回到系统内存。(注:缓存行是指cpu高速缓存中可以分配的最小存储单元。处理器填写缓存    行时会加载整个缓存行)
  • 这个写回内存的操作会使在其他cpu里缓存了该内存地址的数据无效(如果其他处理器对这个数据进行修改操作时,会重新从系统内存把数据读取到处理器缓存里)。

实现原理:

  1. Lock前缀指令会处理器缓存回写到内存
  2. 一个处理器的缓存回写到内存会导致其他处理器的缓存无效((可见性))

volatile的使用优化:

jdk1.7并发包新增一个队列集合类LinkedTransferQueue,它在使用volatile变量时,通过追加字节的方式来优化队列出队和入队的性能。

为什么追加字节(64)可以优化性能?

对于英特尔酷睿i7、酷睿、Atom和NetVurs等处理器的L1,L2或L3缓存高速缓存行是64个字节宽,不支持部分填充缓存行,而一个引用变量占4个字节,就有可能导致多个应用变量一起填充到同一个缓存行中,对缓存行加锁,这样会使得队列的头结点和尾节点可能出现在同一个缓存行中,而队列的入队与出队操作时在不断的修改头结点和尾节点,就可能导致头结点和为节点同时锁定,多处理器下严重影响性能。

以下情况:使用volatile变量是不应该追加到64字节:

  • 缓存行宽度不是64字节(如P6系列和奔腾处理器,L1,L2是32字节宽)
  • 共享变量不会被频繁的写,不频繁写,那么缓存行被锁定的几率很小

 

 灵魂拷问:为什么需要使用volatile??

多线程安全需要考虑原子性,可见性,有序性

而volatile提供了可见性,有序性(不提供原子性,原子性可以使用atomic包,或者synchronized 关键字加锁 )

可见性: A线程修改了volatile 变量,会导致B线程持有得到该变量失效,需要重新到主内存读取,也就是A修改B线程可见

(所有线程对变量的操作都是 现将 数据从主内存读取副本到工作内存(线程私有内存),操作完成之后,刷回主内存),volatile 则可以通知 其他线程 变量失效,需要重新到 主内存读取操作

有序性:volatile 满足happensBefore 原则,如果操作次序可以由原则推出,则不会发生指令重排(通过插入内存屏障,禁用指令与内存屏障前后发生重排;内存屏障的另一个作用是通知cpu 强制刷新缓存,获取最新的值

可见性测试代码:

class Data{
   volatile int number=0;
    public void changeNumber(){
        number=1;
    }
}
public class UnderstandVolatile {
    // 测试可见性
    public static void main(String[] args) {
        Data data=new Data();
        // 1. data 不加volatile   //
        //      结果:CHANGE come in
        //           CHANGE updated value:1
        // 2. data 加上volatile
        // 结果: CHANGE come in
        //       CHANGE updated value:1
        //       main: finished
        // 第一个线程去改数据,用3s 完成数据更改
        new Thread(()->{
           System.out.println(Thread.currentThread().getName()+" come in");
            try {
                Thread.sleep(3*1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            data.changeNumber();
            System.out.println(Thread.currentThread().getName()+" updated value:"+data.number);
        },"CHANGE").start();
        // 第二个线程 main ,等待线程 1 完成数据更改
        while (data.number==0){
        }
        System.out.println("main: finished");
    }
}

 

在什么地方使用volatile?

DCL=Double check       Lock( 双端检测机制,单例模式会用到)

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值