日期:2014-05-20  浏览次数:20778 次

Concurrent Synchronizer Framework(2)

?

这章来自3.1《Synchronization State》和3.2的《Blocking》。

AbstractQueuedSynchronizer会维护一个Int类型的synchronization state。公开了3个访问和更新state的方法,分别是:getState,setState和compareAndSetState。这些方法依赖java.util.concurrent.atomic和volatile。其中compare-AndSetState实现是依赖于CAS(compare-and-swap)或LL/SC(loadlinked/store-conditional)。CAS大家可能比较熟悉,(current ==expect)则设值。LL/SC则比CAS更严格,(current==expect && current 没有被restore)才算竞争成功。current被restore仍然等于current,CAS是竞争成功的。这是ABA问题。是否觉得LL/SC有点AtomicStampedReference的味道?的确,AtomicStampedReference是通过引进版本号来检测是否被restore。

??????? 用一个32位的int来表示synchronization state是一个实用的决定,虽然jsr166提供了64位的long的原子操作,但在一些平台上需要实用内部锁来实现,对性能有所影响。在未来可能引入一个用long来表示synchronization state的基类,但在现在还没有一个足够的理由把这个类包含进来。而且32位的state也满足了大部分应用的需要。只有一个synchronizer CyclicBarrier看起来需要更多的位来表示state。


??????? 继承于AbstractQueuedSynchronizer的具体类必须明确的定义tryAcquiretryRelease方法,用来控制acquire和release操作。如果synchronization 被获取,tryAcquire必须返回true。如果新的synchronization? state允许以后的acquire,tryRelease也必须返回true。这些方法提供一个int的参数来表示希望的state。例如在一个可重入锁里,就需要计数,但对于大多数的synchronizer是可以忽略这个参数的。


在jsr166前,如果不基于嵌入的monitor,是没有可用的java api去block和unblock一个线程的。唯一的候选方法是Thread.spspend和Thread.resume。但是它们不符合要求,原因是:如果一个unblocking的线程在调用suspend被阻塞前调用了resume是没有效果的(这里是希望,如果在调用suspend前resume先被调用,suspend不会导致线程挂起)。

在并发包里包含了一个LockSupport的类,该类解决了这个问题。方法LockSupport.park会block当前线程,除非或直到LockSupport.unpark被调用。unpark的调用是不会被计数的,所以多次unpark只会影响之后的第一次park。然后这些应用是针对每个thread的,而不是每个synchronizer。一个thread在一个新的synchronizer调用了park可能马上返回,因为unpark在之前被调用了。如果没有unpark,那么下次调用park就会导致thread阻塞。可能会需要一个操作去清理thread 的state,使park可以阻塞thread,但是其实不需要这么做,在需要的情况下,多调用几次park更高效。



在某些程度上,这个简单的原理看上去和某些平台的某些机制很相像,例如在Solaris-9的thread library,windows的“consumeable events”,Linux NPTL thread library。park和unpark可以高效的映射到这些常见的支持jvm的平台上。park也支持相对和绝对的超时时间,并且可以和Thead.interrupt结合,interrupt一个thread可以unpark它。

?


?

?

?