线程安全性
一、线程安全性的核心
“共享”意味着变量可以由多个线程同时访问,而“可变”则意味着变量的值在其生命周期内可以发生变化。所以,线程安全性的核心是要对访问操作进行管理,特别是对共享的(shared)和可变的(Mutable)状态的访问。
二、可变状态变量出现错误
对于多线程状态下,“可变” 意味着不安全,如果不进行同步,意味着可能会发生错误。一般有三种方式可以修复这个问题:
- 不在线程之间共享该状态变量
- 将状态变量修改为不可变的变量
- 在访问状态变量时使用同步
三、竞态条件
所谓竞态条件,是指由于不恰当的执行时序而出现不正确的结果,这是一种非常重要的情况。
最常见的竞态条件是“先检查后执行”,如if判断语句、延迟初始化等。if判断语句好理解,那什么是延迟初始化呢?延迟初始化的目的是将对象中的初始化操作推迟到实际被使用时才进行,同时要确保只被初始化一次。
public class LazyInitRace() {
private ExpensiveObject instance = null;
public ExpensiveObject getInstance() {
if (instance == null) {
instance = new ExpensiveObject();
}
return instance;
}
}
这种情况下,假设两个线程A和B同时执行getInstance,A遇到instance为空,创建一个新实例。如果当B检查时instance为空,那么在两次调用getInstance会得到不同的结果。
那么,如何尽可能避免竞态条件呢?
在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中。
四、加锁机制
1. 内置锁 : synchronized
2. 重入 :为每个锁关联一个获取计数值和一个所有者线程。当计数为0时,这个锁就被认为是没有被任何线程持有。当线程请求一个未被持有的锁时,JVM将记下锁的持有者,并将获取计数器值置为1。如果同一个线程再次获取这个锁,计数值将递增,而当线程退出同步代码块时,计数器会相应递减。当计数器为0时,这个锁将被释放。
五、不良并发
所谓不良并发,是指本该能够处理多个请求的程序,因为加了锁,变得每次只有一个线程可以执行。可同时调用的数量,不仅受到可用处理资源的限制,还疏导应用程序本身结构的限制。
可以缩小代码块的同步范围,应该尽量将不影响共享状态且执行时间较长的操作从同步代码块中移出去。当执行时间较长的计算或者无法快速完成的操作时(例如,网络IO或控制台IO),一定不要持有锁。