1、 线程不安全的特征
通过上面一个博文,我们可以注意到不安全例子的特点:
- 有实例变量或静态变量
- 类似于count++(读-操作-写)
- 有if-then
- 有发布set方法
- 多个线程对同一个对象操作
如果有碰到上面这样的,请要注意了!!!
2、不安全的原因
2.1 原子性
- count++ 不是原子操作
- ConcurrentHashMapTest中的if-then也不是原子操作
2.1 有序性
有序性问题(指令重排或者工作内存和主存同步延迟)
见上一篇博客中的——神奇的while例子, 底层可能优化成这样:
while(!done)
i++;
转成这样了:
if(!done) {
while(true)
i++;
}
指令被重排了,工作线程done无法同步到主存中。
2.3 可见性
比如上面的例子,主线程更改了done,但子线程看不到done的变化。
3 保存并发三个特性的手段
- 原子性:AtomicXX、synchronized、使用Map的原子操作。等等
- 有序性: synchronized、volatile
- 可见性: synchronized、volatile、final
!!!万能的synchronized。
4 为什么?
- 为什么AtomicXX有原子性功效?
- 为什么synchronized有原子性、可见性、有序性功效?
- 为什么volatile有可见性、有序性功效?
请看规定的:
1、Java内存模型中的八条可保证happen—before的规则
2、Java内存模型中的八条交互规则
3、volatile特殊规则
套用上面规则来分析synchronized,其他类似。
- 可见性:【规定的某条规则】对一个变量执行unlock之前,必须先把此变量同步回主内存中。
- 有序性和原子性:【规定的某条规则】一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁
5、 synchronized是什么鬼?
javap后的synchronized方法.png 具体方法synchronized修饰的代码库底层指令集有两个: monitorenter/monitorexit
<b>1、 monitor是什么鬼?</b>
<b>2、 为什么会有两个monitorexit</b>
-> 第二个monitorexit是怕你出了异常,做了释放锁的动作。
6、 什么是monitor?
- 中文名称叫做管程
好,自行补脑:
https://zh.wikipedia.org/wiki/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96)
7、为什么要引入管程?
解决类似以下两个问题 from《现代操作系统》:
- 火车订票系统,只有最后一张票,但有2个客户试图争夺这张火车票。
- 线程A负责读取mysql数据,线程B负责打印数据。线程B需要等待A的完成,否则打印就是不正确的。
即: <b>两个或多个线程读写某些共享数据,而最后取决于线程运行的精确时序。——竞争条件</b>
如何避免竞争条件?
——互斥。线程A对共享资源使用完之前线程B无法使用。
互斥的手段:
信号量、互斥量、管程、CPU中断等
8、信号量解决互斥
8.1 什么是信号量
自行补脑: http://www.ruanyifeng.com/blog/2013/04/processes_and_threads.html
8.2 解决互斥栗子(来自《现代操作系统》)
生产者和消费者问题.png
注意:信号量大于=0,如果=0,再去获取信号量的话,就会阻塞住。
问题:交换下生产者中的两行,如下:
Paste_Image.png
- <b>请分析当empty=0和full=10情况,是不是会发生死锁?嘿嘿。。
所以管程的优势是,有编译器来处理进入临界区代码和出临界区代码。我们只要加了synchronized,由编译器自动帮我们编织了monitorenter和monitorexit。
</b>
管程织入
还有一个问题就是进入临界区(共享数据的读写代码段称为临界区)之后,如果里面因为一个条件需要阻塞,那这个锁就无法释放对吧。
所以管程模型,提出了类似于java中的wait/notify/notifyAll(条件队列)用于解决这个问题。当条件阻塞,调用wait让出这个锁。当条件满足notify,让另一个线程重新获得这个锁。
<b>所以wait/notify/notifyAll(条件队列)一定要在synchronized中使用,否则会报错</b>
网友评论