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本身,注意:不是实例);

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

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一样的线程同步(原子性)的效果。

猜你喜欢

转载自juejin.im/post/5d774dbbf265da03d728445a
今日推荐