🍑优化过程中所造成的线程不安全
上述三个是造成线程安全问题的的主要原因,除此之外。在编译器/JVM/操作系统对程序优化的过程中也会好心办坏事,造成线程不安全
1、内存可见性引起的安全问题
分析
🌰栗子
为什么会这种情况呢?
但我们的t1、t2创建好了后,操作系统内存对我们这两个线程进行随机调度执行。那么我们的t1线程在执行过程中就不断地从内存中读取flag的值,然后进行判断。那么就有可能t1线程在执行了好长一会后,t2才执行、输入了一个整数&&改变了flag值。
是操于作系统就发现,怎么回事,这个flag值一直也没有改变,那么t1线程一直不断地从内存中读取flag的值,这样的效率是很低的。于是操作系统就对这种情况进行了优化,t1在第一次从内存中读取flag的值后就不再反复的从内存中读取了,接下来只是不断地进行判断——也就是说:在t2线程把flag的值更改了后,t1线程并没有成功读取到更改后的flag值。
解决方案
再补充一个栗子
比如说接下来我要干三件事
1、去超市闲逛
2、 回家
3、去超市买菜
如果我安装1->2->3的顺序来执行,是不是很傻、很浪费时间。在程序执行的过程中也是如此,通过顺序的改变和调整就可以达到优化的效果。
但有时候顺序是不能随便改变的
编译器 / JVM / 操作系统 优化优化着,还优化出BUG了,那为啥还要有优化呢?
因为不同的程序员的水平差异是非常大的,通过优化,对我们的程序执行效率可能会有翻倍的提升!!!服务器启动的时候,如果加上编译器优化,启动时间10min!但如果关闭优化,启动时间 > 30 min!!!
所以说,不管怎么优化,我们的大前提是要保证程序的逻辑是不变的!我们是希望在逻辑不变的前提下,通过一些优化的操作来提升效率、提高速度!!!
补充一下在单线程下,保证逻辑不变很容易做到。但在多线程环境下,想有在不改变逻辑的前提下优化就变得很困难了——所以才会出现内存可见性问题、指令重排序问题
三、总结
对集合类安全性的一点补充:
线性安全的集合类
Vector:只要是关键性的操作,方法前面都加了synchronized关键字,来保证线程的安全性
Hashtable:使用了synchronized关键字,所以相较于Hashmap是线程安全的。
ConcurrentHashMap:使用锁分段技术确保线性安全,是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector。
线性不安全的集合类
- Hashmap
- Arraylist
- LinkedList
- HashSet
- TreeSet
- TreeMap
Hashmap:HashMap在put操作的时候,如果插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是resize,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
Arraylist: List 对象在做 add 时,执行 Arrays.copyOf 的时候,返回一个新的数组对象。当有线程 A、B… 同时进入 grow方法,多个线程都会执行 Arrays.copyOf 方法,返回多个不同的 elementData 对象,假如,A先返回,B 后返回,那么 List.elementData ==A. elementData,如果同时B也返回,那么 List.elementData ==B. elementData,所以线程B就把线程A的数据给覆盖了,导致线程A的数据被丢失。
LinkedList:与Arraylist线程安全问题相似,线程安全问题是由多个线程同时写或同时读写同一个资源造成的。
HashSet:底层数据存储结构采用了Hashmap,所以Hashmap会产生的线程安全问题HashSet也会产生。
SimpleDateFormat: 对象是线程不安全的