谈谈synchronized和volatile的区别

本文从以下三个方面总结synchronized和volatile的区别:

  • 原子性
  • 内存可见性
  • 重排序

1. 什么是原子性

原子(Atomic)操作指相应的操作是单一不可分割的操作。

如对 int型变量 count 执行 count++ 时就不是原子性操作。该操作分为三个操作:

1)读取变量 count 的当前值 ;
2)拿 count 的当前值 和 1做加法运算;
3) 将 count 的值 加1后的值赋值给 count 变量。

在多线程环境中,非原子性操作可能会受其他线程的干扰。 如此环境中对count 读取时,可能得到的值是“过期”值,该值可能已经被其他线程修改了。

解决办法之一,引入 synchronized 关键字,可以实现原子性操作。其本质是通过该关键字所包含的临界区的排他性 保证在任何一个时刻只有一个线程能够执行临界区的代码。这使得临界区的代码代表了一个原子性操作。

换个角度理解:用 synchronized 加锁后, 谁得到这把锁 就执行谁的代码,执行完后 释放锁。

2.什么是内存可见性

CPU在执行代码时,为了减少变量访问的时间消耗,可能将代码中访问的值缓存到该 CPU 的缓存区中。因此相应的代码再次访问某个变量时,相应的值 可能是从 CPU的缓存 而不从主内存中读取的。 同样地,代码对这些缓存过的变量的值修改也可能仅是被写入 CPU 缓存区,而没有被写入主内存。

由于每个 CPU 都有自己的缓存区,因此一个CPU缓存区中的内容对于其他 CPU 是不可见的。

于是 在其他CPU 上运行的其他线程 可能无法 “看到”该线程对某个变量的值所做的更改。 这就是“内存可见性”。

换个角度理解:CPU为了效率,有个缓存区,同时有个主内存,一个CPU上运行的线程可能在不同的缓存区中修改值,而没有同步到主内存中,其他线程无法看到(无法访问)该线程修改的值。数据没有同步,于是不可见。

小结:synchronized 可实现原子性,同时也能保证内存的可见性(Memory visibility)。

除了synchronized 能保证内存的可见性,还有谁?

于是volatile联想到了,怎么想到的,深入学习Java线程的同学可能都知道。此时应该放到一起比较一下了,便于了解深入同步机制。

volatile 关键字 也能保证内存的可见性,即一个线程对一个采用了 volatile 关键字修饰的变量的值 的更改对于其他线程访问该变量的线程总是可见。

volatile 实现内存可见性的 核心: 当一个线程修改了一个 volatile 修饰的变量时,该值会被写入主内存(RAM),而不仅仅是当前 CPU 的缓存区。而其他CPU 缓存区中存储 该变量的值会因些失效。这就保证了其他线程访问该volatile 修饰的变量时总是可以获取该变量的最新值。

换个角度理解:volatile 会把值写入缓存区和主内存中,既然都写入主内存了,其他线程当然可以访问了,也顺其自然的可见了。

补充一点:volatile 并不能像 synchronized 那样 能够保证操作的原子性。

3. 重排序

volatile 关键字 另一个作用是:禁止 指令的重排序(Re-order)。

(注:此处需要深入理解Java内存模型后才好理解)

一切为了效率,编译器和CPU为了执行效率可能会进行指令的重排序。

举例说明,实例变量的初始语句:

SomeClass someObject = new SomeClass();

我们自然想到执行顺序:

  1. 创建类 SomeClass的实例;
  2. 将 SomeClass的实例引用 赋值给 变量 someObject。

还是为了效率,指令的重排序可能得到的执行顺序:

  1. 分配一段存储 SomeClass 的实例内存空间 (堆空间);
  2. 将 SomeClass的实例引用 赋值给 变量 someObject;
  3. 创建类 SomeClass的实例。

于是问题来了,当其他线程访问someObject 时,得到的仅仅是一段存储 SomeClass的内存空间的引用而已,此时的实例化尚未完成。异常就出现了。

解决思路也很简单,由于时指令的重排序闯祸,禁止就行了。而volatile 就是这个作用。

扩展:在双重检查加锁(DCL)单例模式中也是同样的解决办法。

4. 总结

synchronized和volatile的区别:

  1. synchronized 能保证原子性和可见性

  2. volatile 仅能保证内存可见性

  3. synchronized 会导致上下文切换,volatile不会。

(注:上下文切换是线程状态的切换,会给CPU带来额外的开销)

参考资料:

《Java 多线程编程实战指南(设计模式篇)》 黄文海 (著)

相关推荐
©️2020 CSDN 皮肤主题: 精致技术 设计师:CSDN官方博客 返回首页
实付 9.90元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值