violate能保证可见性但没法提供操做的原子性

简单了解一下Java内存模型。

  1. Java全部变量都存储在主内存中
  2. 每一个线程都有本身独立的工做内存,里面保存该线程的使用到的变量副本(该副本就是主内存中该变量的一份拷贝)
  3. 线程对共享变量的全部操做都必须在本身的工做内存中进行,不能直接在主内存中读写
  4. 不一样线程之间没法直接访问其余线程工做内存中的变量,线程间变量值的传递须要经过主内存来完成。
  5. 线程1对共享变量的修改,要想被线程2及时看到,必须通过以下2个过程:
    • 把工做内存1中更新过的共享变量刷新到主内存中
    • 将主内存中最新的共享变量的值更新到工做内存2中

可见性与原子性

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

原子性:即不可再分了,不能分为多步操做。好比赋值或者return。好比"a = 1;"和 "return a;"这样的操做都具备原子性。相似"a += b"这样的操做不具备原子性,在某些JVM中"a += b"可能要通过这样三个步骤:多线程

1. 取出a和b
2. 计算a+b
3. 将计算结果写入内存
复制代码

Synchronized:保证可见性和原子性

Synchronized可以实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁(对象锁,类锁)时的工做步骤:函数

  1. 先清空工做内存
  2. 在主内存中拷贝最新变量的副本到工做内存
  3. 执行完代码
  4. 将更改后的共享变量的值刷新到主内存中
  5. 释放互斥锁。

PS:优化

  1. 当synchronized修饰一个static方法时,多线程下,获取的是类锁(即Class自己,注意:不是实例);spa

  2. 当synchronized修饰一个非static方法时,多线程下,获取的是对象锁(即类的实例对象)线程

Volatile:保证可见性,但不保证操做的原子性

  1. Volatile实现内存可见性是经过内存屏障store和load指令完成的;code

  2. 对volatile变量执行写操做时,会在写操做后加入一条store指令,即强迫线程将最新的值刷新到主内存中;对象

  3. 而在读操做时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性,例如:排序

    Private int Num=0;
     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这些操做。

  1. 在双重检查单例模式下使用 Volatile 来保证线程安全的原理就是利用了 Volatile 修饰的变量由于插入了内存屏障,禁止了指令重排,从而保证了有序性.即 User user = new User()这个操做,本来的是会被分解为三个操做:

    (a)给Singleton的实例分配内存
     (b)调用Singleton()的构造函数,初始化成员字段;
     (c)将singleton对象指向分配的内存空间(即singleton不为空了);
    复制代码

变量加上volatile能够防止对象初始化的无序性,对象必须初始化完成后才将地址返回。

Synchronized和 Volatile 的比较

  1. Synchronized保证内存可见性和操做的原子性
  2. Volatile只能保证内存可见性
  3. Volatile不须要加锁,比Synchronized更轻量级,并不会阻塞线程(volatile不会形成线程的阻塞;synchronized可能会形成线程的阻塞。)
  4. volatile标记的变量不会被编译器优化,而synchronized标记的变量能够被编译器优化(如编译器重排序的优化).
  5. volatile是变量修饰符,仅能用于变量,而synchronized是一个方法或块的修饰符。
  6. volatile本质是在告诉JVM当前变量在寄存器中的值是不肯定的,使用前,须要先从主存中读取,所以能够实现可见性。而对n=n+1,n++等操做时,volatile关键字将失效,不能起到像synchronized同样的线程同步(原子性)的效果。
相关文章
相关标签/搜索