1. 什么是线程安全性
当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。再解释一下就是:
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
无状态对象一定是线程安全的,而大多数 Servlet 都是无状态的,从而极大地降低了在实现 Servlet 线程安全时的复杂性。只有当 Servlet 在处理请求时需要保存一些信息,线程安全才会成为一个问题。
2. 原子性
在多线程中进行count++
的操作是很不安全的。这看上去仅仅是一个操作,但这个操作并非原子的,因而它并不会作为一个不可分割的操作来执行。
实际上,它可以分解为三个独立的操作:读取 count 的值,将其值加 1,再将结果写入 count。即:读取-->修改-->写入
的操作序列,使得结果状态依赖前面的状态。
因此,如果在两个线程没有同步的情况下,发生了如下状态:
也就是如果多个线程的操作交替执行,那么会导致这两个线程读取到的值相同的,同时执行加 1 操作,得到的结果也就是相同的。而这不是我们希望的结果。
原子性也可以被解释为不可分割的操作。
2.1 竞态条件
当某个计算的正确性取决于多个线程的交替执行时序时,就会发生竞态条件。也就是说正确的结果需要靠运气。因此,竞态条件并不总是会产生错误,而是在某种不恰当的执行时序情况下会。最常见的竞态条件是“先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步动作。
举个例子:延迟初始化中的竞态条件
延迟初始化的目的是将对象的初始化操作推迟到实际被使用是才进行,同时要确保只被初始化一次。
@NotThreadSafe
public class LazyInitRace{
private ExpensiveObject instance = null;
public ExpensiveOvject getInstance(){
if (instance == null)
instance = new ExpensiveObject();
return instance;
}
}
上面的 LazyInitRace 类说明了这个问题。首先 getInstance 方法将判断 ExpensiveObj