什么是线程安全性?
线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。
以下两种情况,一定是线程安全的:
- 无状态对象一定是线程安全的。
- 当在无状态的类中添加一个状态时,如果该状态完全由线程安全的对象来管理,那么这个类依然是线程安全的。
下面给出一个无状态对象的例子:
//这是一个实现因数分解功能的Servlet
public class StatelessFactorizer implements Servlet {
public void service(servletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
在这个例子中,StatelessFactorizer是没有状态的,所以这是一个线程安全类。
现在,我们向这个类添加一个状态,用来记录请求数量。
public class Factorizer implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public void service(servletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
}
在这里,我们使用了AtomicLong对象来记录命中次数,这个类可以保证对它进行的操作是原子性的。当前类的状态就是这个计数器的状态,而这个计数器是线程安全的,所以这个类也是线程安全的。
原子性
对于上述的计数器,我们不采用AtomicLong,而是使用Long,如下:
public class Factorizer implements Servlet {
private Long count = 0;
public void service(servletRequest req, ServletResponse resp){
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
++count;
encodeIntoResponse(resp, factors);
}
}
这个时候Factorizer类就再是线程安全的。因为++count这个操作不是原子的,它包含了三个独立的操作:读取count的值,将值加1,将结果写入count。也就是说,其结果状态依赖于之前的状态,存在竞态条件。
竞态条件:由于不恰当的执行时序而出现不正确的结果。
最常见的竞态条件类型就是“先检查后执行(Check-Then-Act)”操作,即通过一个可能失效的观测结果来决定下一步的动作。
例如
public class LazyInit {
private Object instance = null;
public Object getInstance() {
if (instance == null)
instance = new Object();
return instance;
}
}
要避免竞态条件问题,就必须在某个线程修改该变量时,通过某种方式防止其他线程使用这个变量,从而确保其他线程只能在修改操作完成之前或之后读取和修改状态,而不是在修改状态过程中。
锁机制
锁能够保证被其保护的代码块以串行方式访问,以保证其状态的一致性。
Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包含两个部分:一个是作为锁的对象的引用,一个作为由这个锁保护的代码块。以关键字synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象作为锁。
Java的内置锁是一种互斥锁,最多只能由一个线程获得该锁。同时,Java的内置锁是可以重入的。
重入:当某个线程试图获得一个由它自己持有的锁时,这个请求会成功。
如果在复合操作的执行过程中持有一把锁,那么会使复合操作编程原子操作。需要注意的是,无论是写入或是访问共享变量,都需要使用同步,并且每一个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
只有被多个线程同时访问的可变数据才需要用锁来保护。并且对于每个包含多个变量的不变性条件,其中设计的所有变量都需要由同一个锁来保护。