多线程场景下编码的一些思考

背景编写一个单例的实现 。这里采用一个双重检查方式 。

多线程场景下编码的一些思考

文章插图
【多线程场景下编码的一些思考】image.png
发现是有问题的 。主要是编译优化导致new 对象的顺序和可见性问题 。
问题修复 :只需要再单例对象 加上 volatile 修饰即可 。
分析背后的原因多线程编程中主要核心关注如下三个点 :
可见性缓存带来的原子性 (硬件的坑)
原子性线程切换带来的原子性
有序问题编译优化,内存模型一般原则(HAppens-Before)
多线程场景下编码的一些思考

文章插图
image.png
这里给出一个volatile 修饰的对象 new 对象在编译后的执行字节码流程,可以看出没有做编译优化和顺序重排 。
JAVA并发常见的内存模型valatile :禁这条规则是指一个valatile 变量的写操作
Happens-Before 于后续对这个 valatile变量的读操作
le变量的读操作
final :编译优化更好点 ,生而不变,可以使劲的优化
传递性: 
Happens-Before: 前面的操作结果对后续的操作是可见的
x = 7 y = 8 在多线程中,
传递性 是指 :
线程A:写 X =7 Y =8 成功
线程B:如果读到 y = 8 ,
那么他读到的X 肯定是7
Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分 。
而管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程 。但是管程在利用OOP的封装特性解决了信号量在工程实践上的复杂性问题,因此java采用管理机制 。
因此java
就是将共享变量及其对共享变量的操作统一封装起来 。在下图中,管程 X 将共享变量 queue 这个队列和相关的操作入队 enq()、出队 deq() 都封装起来了;线程 A 和线程 B 如果想访问共享变量 queue,只能通过调用管程提供的 enq()、deq() 方法来实现;enq()、deq() 保证互斥性,只允许一个线程进入管程 。管程模型和面向对象高度契合的 。
多线程场景下编码的一些思考

文章插图
image.png
管程如何解决线程间的同步问题呢?Java 参考了 MESA 模型,语言内置的管程(synchronized)对 MESA 模型进行了精简 。MESA 模型中,条件变量可以有多个,Java 语言内置的管程里只有一个条件变量 。具体如下图所示 。
多线程场景下编码的一些思考

文章插图
image.png
JAVA SDK并发包通过Lock 和Condition两个接口’又’实现了一次管程 。LOCK 解决互斥,Condition解决同步问题
重复造轮子
1.能够响应终端
2.支持超时
3. 非阻塞获取锁
多线程场景下编码的一些思考

文章插图
image.png
轮子的理由:
1.能够响应终端
2.支持超时
先找到一个 阻塞的线程 看执行栈信息
多线程场景下编码的一些思考

文章插图
image.png

多线程场景下编码的一些思考

文章插图
image.png




    推荐阅读