Java 并发学习笔记(一)——原子性、可见性、有序性问题

发布时间:2019-11-19 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Java 并发学习笔记(一)——原子性、可见性、有序性问题脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

计算机的 CPU、内存、I/O 设备的速度一直存在较大的差异,依次是 CPU > 内存 > I/O 设备,为了权衡这三者的速度差异,主要提出了三种解决办法:

  • CPU 增加了缓存,均衡和内存的速度差异
  • 发明了进程、线程,分时复用 CPU,提高 CPU 的使用效率
  • 编译指令优化,更好的利用缓存

三种解决办法虽然有效,但是也带来了另外的三个问题,分别就是并发 bug 产生的头。

1.可见性问题

如果是单核 CPU,多个线程操作的都是同一个 CPU 缓存,那么一个线程修改了共享变量,另一个线程肯定能马上看到。

如果是多核 CPU ,每个 CPU 都有自己的缓存,这样线程对共享变量的修改便对其他线程不可见了。

Java 并发学习笔记(一)——原子性、可见性、有序性问题

2.原子性问题

为什么会有线程切换?一个线程在执行的过程中,可能会进行耗时的 I/O 操作,这时线程需要等待 I/O 操作完成。线程在等待的过程中,可以释放 CPU 的使用权,让另一个线程执行,这样能够提高 CPU 的使用率。

Java 并发学习笔记(一)——原子性、可见性、有序性问题

例如上图,两个线程同时对变量 count 加 1,线程 A 在执行的过程中切换到了线程 B,最后导致写入到内存的值都是 1,与预期不符。

3.有序性问题

首先看一段很经典的获取单例对象的代码:

public class Singleton {          PRivate static Singleton instance;      //Java 获取单例对象     public Singleton getInstance(){         if (instance == null){             synchronized (Singleton.class){                 if (instance == null){                     instance = new Singleton();                 }             }         }         return instance;     } }

程序的逻辑是:首先判断 instance 是否为空,如果为空,对其加锁,然后再判断是否为空,此时为空的话则初始化 instance 对象。

如果线程 A 和 B 同时执行方法,在 synchronized 处,一个线程会被阻塞,假设被阻塞的是线程 B,此时线程 A 进入并初始化 instance,然后唤醒线程 B,线程 B 进入的时候,发现 instance 不为空了,所以不会创建对象。

但是因为有序性问题的存在,这段代码也不是想象的那么完美,我们期望的初始化对象的过程是这样的:1.分配内存;2.初始化对象;3.将内存地址赋给 instance。但是经过编译优化之后,却是这样的:

  • 1.分配内存
  • 2.将内存地址赋给 instance
  • 3.在内存上面初始化对象

这样,如果线程 A 执行到了第二步,然后切换到 线程 B,线程 B 就会认为 instance 不为空然后直接返回了,实际上 instance 并没有初始化。

最后,总结一下,导致并发问题的三个源头分别是

  • 原子性:一个线程在执行的过程当中不被中断。
  • 可见性:一个线程修改了共享变量,另一个线程能够马上看到,就叫做可见性。
  • 有序性:编译指令重排导致的问题。

脚本宝典总结

以上是脚本宝典为你收集整理的Java 并发学习笔记(一)——原子性、可见性、有序性问题全部内容,希望文章能够帮你解决Java 并发学习笔记(一)——原子性、可见性、有序性问题所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。