java并发编程实战-volatile

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

非原子的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变量时总会返回最新写入的值jvm

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

当且知足如下全部条件,才应该使用volatile变量优化

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

                    2.该变量不会与其余状态变量一块儿归入不变性条件中spa

                    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

相关文章
相关标签/搜索