Java JUC系列目录链接
Java 线程池核心原理解析
- 何为线程安全
- 如何解决线程安全
- 结语
何为线程安全
直接上个伪代码:
boolean a = 多个线程共享变量;
boolean b = 存在写操作;
boolean c = 写操作导致数据冲突;
if( a && b && c ) {
存在线程安全问题;
}
文字描述就是,只有
多个线程共享变量
,
线程存在写操作
,
操作导致数据冲突
同时满足的情况下,才会存在线程安全问题。
如何解决线程安全
解决线程安全还不简单,不就是让上面的if的条件不为真嘛!显而易见,只要a,b,c中有一个或多个恒为false,就不会出现线程安全问题。
下面我们就给出具体解决方案:
-
‘a’ always == false:
即:多个线程不共享变量。在这之前我们先来想一下,为什么多个线程会共享变量?联系Java虚拟机模型,我们知道,如果我们的变量是存在堆区的,那么这个变量就会被多个线程所共享。明白了这一点就好办了,
*方案1
我们直接把变量定义到栈,即局部变量,那不就不会跟其他线程共享啦,自然旧解决了线程安全的问题。
伪代码如下:
class A { // int a = 1; public void test(){ int a = 1; } }
我们把原来的全局变量a移到了局部变量,这样a就会被存放到方法’test’的方法栈中,自然就不会有线程安全的问题了。
如果说你非要犟,我就是要把变量存到堆中去,也要不让线程之间共享变量,那怎么办?
别慌,我们还是有办法的,如果大家听过ThreadLocal这个类,那一定不会束手无策了。
ThreadLocal为每一个线程需要的变量提供一个本地的副本,这样就不会有线程安全问题啦。什么意思?简单来说就是,你线程不是想要a变量嘛,我给你们线程没人一个a变量,但是你们都只能各玩各的,不许去玩别人的。但是使用ThreadLocal有一点要注意,你创建的这个对象如果存放在堆中的话,还是可能会导致线程安全问题的。*方案2
-
‘b’ always == false:
即:线程不存在写操作。既然你说写操作会有线程安全问题,那我们来个绝的,既然写了问题这么多,大家都别想写了!
直接把变量变成final类型,直接上伪代码:*方案3
虽然这个解决方案有点绝,但也不失为一总可选方案呀。class A { final int a = 1; }
-
‘c’ always == false:
即:写操作不导致数据冲突。写操作不冲突?emm…让我想想,既然你们都一起来搞会有冲突,那这样,你们排队一个个来玩,那不就完了嘛。这就是我们最熟悉的——‘锁’。
*方案4
我们使用锁来确保线程依次对变量进行操作,这样就避免了线程安全问题,至于怎么加锁synchronized,Lock啥的随便你选,我就不说了。
你说什么?加锁性能低?没办法了,只能掏出这个了——‘CAS’,即为乐观锁。想要确保线程的写操作不导致数据冲突,加锁确实是性能较低的一种解决方案,乐观锁看起来名字像是锁,其实是一种无锁机制,原理想必你们背面试题肯定是十分熟练了。顺便一提的是,传统CAS可能会导致‘ABA’问题,所以有人在乐观锁的实现中加入了时间戳(版本号)。
结语
本文只是从广度上简介了关于线程安全的一些问题,关于文中提到的一些深度问题,比如jvm模型,ThreadLocal啥的与本文无关,这里就不再赘述了。