《Java Concurrency in Practice》之加锁机制(Locking)

@NotThreadSafe
public class UnsafeCachingFactorizer extends HttpServlet {

	private static final long serialVersionUID = 1L;
	private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();
	private final AtomicReference<BigInteger []> lastFactors = new AtomicReference<BigInteger []>();

	/* (non-Javadoc)
	 * @see javax.servlet.Servlet#service(javax.servlet.ServletRequest, javax.servlet.ServletResponse)
	 */
	@Override
	public void service(ServletRequest req, ServletResponse resp)
			throws ServletException, IOException {
		BigInteger i = extractFromRequest(req);
		if (i.equals(lastNumber.get())) {
			encodeIntoResponse(resp, lastFactors.get());
		} else {
			BigInteger [] factors = factor(i);
			lastNumber.set(i);
			lastFactors.set(factors);
			encodeIntoResponse(resp, factors);
		}
	}
}

        阅读上面的代码后,不难发现,尽管使用了线程安全的状态变量,但依然存在着竞态条件(Race conditions)。

        在线程安全性的定义中明确声明,多个线程之间的操作无论采用何种执行时序或交叉操作方式,都要保证不变性(invariants)条件不被破坏。UnsafeCachingFactorizer的不变性条件之一是:在lastFactors中缓存的因数之积应该等于在lastNumber中缓存的值。只有确保了这个不变性条件不被破坏,程序才是正确的。

        要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。

一、内置锁(Intrinsic locks)

        Java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block)。同步代码块包括两个部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字synchronized来装饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静态的synchronized方法以Class对象为锁。

        每个Java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic locks)或监视器锁(Monitor Lock)。

        Java的内置锁相当于一个互斥体(或互斥锁),这意味着最多只有一个线程能持有这种锁。

@ThreadSafe
public class SynchronizedFactorizer extends HttpServlet {
    // ... ...
    
    public synchronized void service(ServletRequest req, ServletResponse resp) { // ... ...}
}
二、重入(Reentrancy)
public class Widget {

	public synchronized void doAnything() { }
	
}
public class LoggingWidget extends Widget {

	@Override
	public synchronized void doAnything() {
		super.doAnything();
	}

}
        Reentrancy,意味着获取锁的操作粒度是线程,而不是调用。进一步提升了加锁行为的封装性。

        Reentrancy的一种实现方式是:为每个锁关联一个获取计数值和一个所有者线程。

三、用锁来保护状态(Guarding state with locks)

        由于锁能使其保护的代码路径以串行形式来访问,因此可以通过锁来构造一些协议以实现对共享状态的独占访问。

        对于可能被多个线程同时访问的可变状态变量,在访问它时都需要持有同一个锁,在这种情况下,我们称状态变量是由这个锁保护的。

        每个共享的和可变的变量都应该只由一个锁来保护,从而使维护人员知道是哪一个锁。

        对于每个包含多个变量的不变性条件,其中,涉及的所有变量都需要由同一个锁来保护。

四、活跃性与性能(Liveness and performance)

        通常,在简单性与性能之间存在着相互制约因素。当实现某个同步策略时,一定不要盲目地为了性能而牺牲简单性(可能会破坏安全性)。

        当执行时间较长的计算或者可能无法快速完成的操作时(例如,网络I/O或控制台I/O),一定不要持有锁。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值