转载

violate能保证可见性但无法提供操作的原子性

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

原子性:即不可再分了,不能分为多步操作。比如赋值或者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本身,注意:不是实例);

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

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

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

  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可以防止对象初始化的无序性,对象必须初始化完成后才将地址返回。

原文  https://juejin.im/post/5d774dbbf265da03d728445a
正文到此结束
Loading...