java并发编程实战-volatile

https://blog.csdn.net/u014108122/article/details/38173201

非原子的64位操作:

                    非volatile类型的64位数值变量(double和long)。java内存模型要求,变量的读取操作和写入操作都必须是原子操作,但对于非volatile类型的long和double类型,jvm允许将64位的读操作和写操作分解为2个32位操作。当读取一个非volatile类型的long变量时,如果该变量的读写操作在不同的线程中执行,很有可能读取到某个值得高32位和另外 一个值的低32位。

                    因此,在多线程程序中使用共享且可变的long和double变量也是不安全的,除非用volatile声明,或者用锁保护起来

volatile变量是一种比sychronized关键字更轻量的同步机制

volatile变量:编译器与运行时都会注意到这个变量是共享的,因为不会将该变量上的操作与其他内存操作一起重排序,volatile变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取volatile变量时总会返回最新写入的值

volatile变量使用场景:  通常用做某个操作完成,发生中断或者状态的标志

当且满足以下所有条件,才应该使用volatile变量

                    1.在访问变量时不需要加锁

                    2.该变量不会与其他状态变量一起纳入不变性条件中

                    3.对变量的写入操作不依赖变量的当前值,或者你能确保只有单个线程更新变量的值

加锁机制既可以保证可见性又可以保证原子性,而volatile变量只能确保可见性

原子性

原子是世界上的最小单位,具有不可分割性。比如 a=0;(a非long和double类型) 这个操作是不可分割的,那么我们说这个操作时原子操作。再比如:a++; 这个操作实际是a = a + 1;是可分割的,所以他不是一个原子操作。非原子操作都会存在线程安全问题,需要我们使用同步技术(sychronized)来让它变成一个原子操作。一个操作是原子操作,那么我们称它具有原子性。java的concurrent包下提供了一些原子类,我们可以通过阅读API来了解这些原子类的用法。比如:AtomicInteger、AtomicLong、AtomicReference等。

可见性

可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的。也就是一个线程修改的结果。另一个线程马上就能看到。比如:用volatile修饰的变量,就会具有可见性。volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存。所以对其他线程是可见的。但是这里需要注意一个问题,volatile只能让被他修饰内容具有可见性,但不能保证它具有原子性。比如 volatile int a = 0;之后有一个操作 a++;这个变量a具有可见性,但是a++ 依然是一个非原子操作,也就这这个操作同样存在线程安全问题。

他们之间关系

原子性是说一个操作是否可分割。可见性是说操作结果其他线程是否可见。这么看来他们其实没有什么关系。

 

案例:

  1. package com.chu.test.thread;  
  2. /** 
  3.  * 可见性分析 
  4.  * @author Administrator 
  5.  * 
  6.  *volatile 会拒绝编译器对其修饰的变量进行优化。也就不会存在重排序的问题。volatile只会影响可见性,不会影响原子性。 
  7.  *下面程序如果不加 
  8.  */  
  9. public class Test {  
  10.   
  11.     volatile int a = 1;  
  12.     volatile boolean ready;  
  13.       
  14.     public class PrintA extends Thread{  
  15.         @Override  
  16.         public void run() {  
  17.             while(!ready){  
  18.                 Thread.yield();  
  19.             }  
  20.             System.out.println(a);  
  21.         }  
  22.     }  
  23.     public static void main(String[] args) throws InterruptedException {  
  24.         Test t = new Test();  
  25.         t.new PrintA().start();  
  26.         //下面两行如果不加volatile的话,执行的先后顺序是不可预测的。并且下面两行都是原子操作,但是这两行作为一个整体的话就不是一个原子操作。  
  27.         t.a = 48; //这是一个原子操作,但是其结果不一定具有可见性。加上volatile后就具备了可见性。  
  28.         t.ready = true;//同理  
  29.     }  
  30.   
  31.  

  32.  

    来源: http://blog.csdn.net/maosijunzi/article/details/18315013

  在说明Java多线程内存可见性之前,先来简单了解一下Java内存模型。

     (1)Java所有变量都存储在主内存中

     (2)每个线程都有自己独立的工作内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)

 

  (1)线程对共享变量的所有操作都必须在自己的工作内存中进行,不能直接在主内存中读写

   (2)不同线程之间无法直接访问其他线程工作内存中的变量,线程间变量值的传递需要通过主内存来完成。

线程1对共享变量的修改,要想被线程2及时看到,必须经过如下2个过程:

   (1)把工作内存1中更新过的共享变量刷新到主内存中

   (2)将主内存中最新的共享变量的值更新到工作内存2中

 

可见性与原子性

   可见性:一个线程对共享变量的修改,更够及时的被其他线程看到

   原子性:即不可再分了,不能分为多步操作。比如赋值或者return。比如"a = 1;"和 "return a;"这样的操作都具有原子性。类似"a += b"这样的操作不具有原子性,在某些JVM中"a += b"可能要经过这样三个步骤:

① 取出a和b

② 计算a+b

③ 将计算结果写入内存

 

(1)Synchronized:保证可见性和原子性

    Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁时,先清空工作内存→在主内存中拷贝最新变量的副本到工作内存→执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。

 

(2)Volatile:保证可见性,但不保证操作的原子性

    Volatile实现内存可见性是通过store和load指令完成的;也就是对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性,例如:

[java] view plain copy

  1. Private int Num=0;  
  2. Num++;//Num不是原子操作  

    Num不是原子操作,因为其可以分为:读取Num的值,将Num的值+1,写入最新的Num的值。

    对于Num++;操作,线程1和线程2都执行一次,最后输出Num的值可能是:1或者2

   【解释】输出结果1的解释:当线程1执行Num++;语句时,先是读入Num的值为0,倘若此时让出CPU执行权,线程获得执行,线程2会重新从主内存中,读入Num的值还是0,然后线程2执行+1操作,最后把Num=1刷新到主内存中; 线程2执行完后,线程1由开始执行,但之前已经读取的Num的值0,所以它还是在0的基础上执行+1操作,也就是还是等于1,并刷新到主内存中。所以最终的结果是1

    一般在多线程中使用volatile变量,为了安全,对变量的写入操作不能依赖当前变量的值:如Num++或者Num=Num*5这些操作。

 

(3)Synchronized和Volatile的比较

    1)Synchronized保证内存可见性和操作的原子性

    2)Volatile只能保证内存可见性

    3)Volatile不需要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。)

    4)volatile标记的变量不会被编译器优化,而synchronized标记的变量可以被编译器优化(如编译器重排序的优化).

    5)volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。

      volatile本质是在告诉JVM当前变量在寄存器中的值是不确定的,使用前,需要先从主存中读取,因此可以实现可见性。而对n=n+1,n++等操作时,volatile关键字将失效,不能起到像synchronized一样的线程同步(原子性)的效果。

 

【参考资料】《细说Java多线程之内存可见性》http://www.imooc.com/video/6775(含视频和代码)

【相关习题】

 

(1)下列说法不正确的是()

A.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行。

B.当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。

C.当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问不会被阻塞。

D.当一个线程访问object的一个synchronized(this)同步代码块时,它就获得了这个object的对象锁。结果,其它线程对该object对象所有同步代码部分的访问都被暂时阻塞。

答案:C,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将会被阻塞。

(2)下面叙述错误的是:

A.通过synchronized和volatile都可以实现可见性

B.不同线程之间可以直接访问其他线程工作内存中的变量

C.线程对共享变量的所有操作都必须在自己的工作内存中进行

D.所有的变量都存储在主内存中

答案:B,不同线程之间无法直接访问其他线程工作内存中的变量

来源: http://blog.csdn.net/guyuealian/article/details/52525724

原子性AtomicInteger 实现说明:http://blog.csdn.net/samjustin1/article/details/52254636

 

转载于:https://my.oschina.net/u/3098425/blog/1814473

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值