线程安全性

Aug 17, 2016


  要编写线程安全的代码,其核心在于对对象状态的访问进行管理,特别是对共享的和可变的状态的访问。一提到java同步,除了想到synchronized加锁方式,还应该想到volatile变量,显示锁以及原子变量。

一、线程的安全性

  当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能够表现出正确的行为,那么就称这个类是线程安全的。在线程安全类中,封装了必要的同步机制,因此客户端无须进一步的采取同步措施。
  无状态的对象一定是线程安全的。

二、原子性

  什么是操作的原子性,个人理解原子性是指一个或者一系列操作过程中,所依赖的中间变量或者状态,都不应该被其他线程修改。在并发编程中,由于不恰当的执行时序而出现的不正确的结果,这种情况称为竞态条件

  为了保证线程安全性,“先检查后执行”和“读取-修改-写入”等操作必须是原子的。这些操作被称为符合操作。这段代码使用了java.util.concurrent.atomic包中的原子变量。

@ThreadSafe
public class CountingFactorizer 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);
    }

}

  在实际情况中,应尽可能的使用现有的线程安全对象来管理类状态。与非线程安全对象相比,判断线程安全对象的可能状态及其状态 转换情况要更为容易。

三、volatile变量和加锁机制

  要保持原子的一致性,就需要在单个原子操作中更新所有相关的变量。对于可能被多个线程同时访问的变量,在访问它时都需要持有同一个锁,按在这种情况下,我们成状态变量由这个锁保护。每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
  volatile变量相比于锁是一种稍弱的同步机制。用来确保变量的更新操作通知到其他线程。当把变量声明为volatile后,编译器不会讲该变量上的操作与其他内存操作一起重排序。volatile变量不会被缓存在寄存器或者其他处理器不可见的地方,因此在读取volatile变量的时候总会返回最新的返回值。
  java的锁有内置锁和可重入锁。一般情况下选用内置锁就可以了, 可重入锁需要自己释放锁,使用更加灵活。可以有效的避免内置锁死锁造成的程序无法恢复。加锁机制可以确保可见性和原子性,而volatile变量只能确保可见性。