线程安全性
什么是线程安全性
-
某个类的行为与其规范完全一致。在良好的规范中通常会定义各种不变性条件(Invariant)来约束对象的状态,以及定义各种后验条件(Postcondition)来描述对象操作的结果。由于我们通常不会为类编写详细的规范,那么如何知道这些类是否正确呢?我们无法知道,但这并不妨碍我们在确信“类的代码能工作”后使用它们。这种“代码可信性”非常接近于我们对正确性的理解,因此我们可以将单线程的正确性近似定义为“所见即所知(we know it when we see it)”。在对“正确性”给出了一个较为清晰的定义后,就可以定义线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的.
-
无状态对象一定是线程安全的
原子性
-
原子性是指操作是不可分的。其表现在于对于共享变量的某些操作,应该是不可分的,必须连续完成。例如a++,对于共享变量a的操作,实际上会执行三个步骤,1.读取变量a的值 2.a的值+1 3.将值赋予变量a 。 这三个操作中任何一个操作过程中,a的值被人篡改,那么都会出现我们不希望出现的结果
竟态条件
-
当两个线程竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件
-
延迟初始化中的竞态条件
1 @NotThreadSafe 2 3 public class LazyInitRace{ 4 5 private ExpensiveObject instance=null; 6 7 public ExpensiveObject getInstance(){ 8 9 if(instance==null) 10 11 instance=new ExpensiveObject(); 12 13 return instance; 14 15 } 16 17 }
假定线程A和线程B同时执行getInstance。A看到instance为空,因而创建一个新的ExpensiveObject实例。B同样需要判断instance是否为空。此时的instance是否为空,要取决于不可预测的时序,包括线程的调度方式,以及A需要花多长时间来初始化ExpensiveObject并设置instance。如果当B检查时,instance为空,那么在两次调用getInstance时可能会得到不同的结果,即使getInstance通常被认为是返回相同的实例
要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态的过程中
复合操作
-
为了确保线程安全性,“先检查后执行”(例如延迟初始化)和“读取-修改-写入”(例如递增运算)等操作必须是原子的。我们将“先检查后执行”以及“读取-修改-写入”等操作统称为复合操作:包含了一组必须以原子方式执行的操作以确保线程安全性
加锁机制
-
Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)
-
每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)
-
1 synchronized(lock){ 2 3 //访问或修改由锁保护的共享状态 4 5 }

浙公网安备 33010602011771号