概述
多线程三大特性:原子性、可见性、有序性。
1. 原子性
原子性是指:多个操作作为一个整体,不能被分割与中断,也不能被其他线程干扰。如果被中断与干扰,则会出现数据异常、逻辑异常。
多个操作合并的整体,我们称之为复合操作。一个复合操作,往往存在前后依赖关系,后一个操作依赖上一个操作的结果。如果上一个操作结果被其他线程干扰,对于当前线程看来整个复合操作的结果便不符合预期。同理线程也不能在复合操作中间被中断,中断必须发生在进入复合操作之前或者等到复合操作结束之后。
保证原子性就是在多线程环境下,保证单个线程执行复合操作符合预期逻辑。
典型的复合操作:『先检查后执行』和『读取—修改—写入』
1.1 先检查后执行
@NotThreadSafe
public class LazyInitClass {
private static LazyInitClass instance ;
public static LazyInitClass getInstance() {
if(instance == null)
instance = new LazyInitClass() ;
return instance ;
}
}
LazyInitClass
的 getInstance
中包含先检查后执行的复合操作,通常我们也可以称 getInstance
中包含竞态条件。假设线程 A 和线程 B 同时执行 getInstance
。A 看到 instance
为空,便执行 new LazyInitClass()
逻辑。A 还未完成初始化并设置 instance
,B 检查 instance
,此时 instance
为空,B 便也会执行 new LazyInitClass()
。那么两次调用 getInstance
时可能会得到不同的结果。通常 getInstance
的预期结果是多次调用得到相同的对象实例。
LazyInitClass
的 getInstance
方法虽然存在竞态条件,多数情况下并不会造成业务异常,影响仅仅是增加了 JVM 垃圾回收负担而已。这也是多线程问题隐蔽性强且偶发的原因之一。
但话说回来,编程原则之一就是所有逻辑都必须建立在确定性之上,任何建立在不确定性上的逻辑都是隐患。虽然从业务上看多数情况下没问题,但竞态条件的存在,让代码逻辑建立在不确定性之上。作为编码者应该重视此类问题。
1.2 读取—修改—写入
@NotThreadSafe
public class ReadModifyAndWriteClass {
private int count = 0 ;
public int increase() {
return count++