当做个线程访问某个类时,这个类始终都能表现出真确的行为,那么就称这个类是线程安全的。
线程安全类可以认为是一个在并发环境和单线程环境中都不会被破坏的类。
- 无状态的对象一定是线程安全的。
- 原子性
- 竞态条件:在并发编程中,由于不恰当的执行时序而出现的不正确的结果是一种非常重要的情况,这种情况通常称为竞态条件。【当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竞态条件】常见的就是:先检查后执行
- 数据竞争:很容易和竞态条件混淆。如果访问共享的飞final类型的域时,没有采用同步机制来进行协同,那么就会出现数据竞争。
- 原子操作:对于访问同一个状态的所有操作(包括该操作本身)来说,这个操作是一个以原子方式执行的操作。
- 应当尽可能的使用现有的线程安全对象(例如:AtomicLong)来管理类的状态。
@ThreadSafe
public class CountingFactorizer extends GenericServlet implements Servlet {
private final AtomicLong count = new AtomicLong(0);
public long getCount() { return count.get(); }
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
count.incrementAndGet();
encodeIntoResponse(resp, factors);
}
void encodeIntoResponse(ServletResponse res, BigInteger[] factors) {}
BigInteger extractFromRequest(ServletRequest req) {return null; }
BigInteger[] factor(BigInteger i) { return null; }
}
- 要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。
- 内置锁【Intrinsic Lock或者称为监视锁[Monitor Lock]】:java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)
- 重入:重入进一步提升了加锁行为的封装性,因此简化了面向对象并发代码的开发。内置所是可以重入的,因此如果某个线程试图获取一个已经由它自己只有的锁,那么这个请求就会成功。意味着获取锁的操作的粒度是“线程”,而不是“调用”
如果内置所不是可以重入的,那么下面这段代码将发生死锁:
public class Widget{
public synchronized void doSomething(){
....
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
System.out.println(toString() +": calling doSomething");
super.doSomething();
}
}
- 用锁来保护状态:对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。
- 每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。
- 活跃性和性能:活跃性没有明确的定义。安全性的含义是“永远不发生糟糕的事情”,而活跃性则关注于另一个目标,即“某件正确的事情最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的无限循环,从而使循环之后的代码无法得到执行,性能问题在过多加锁以及没有正确加锁的情况下会导致性能问题。
- 不良并发【poor Concurrency】:对servlet加锁,会导致用户请求排队等待处理。
网友评论