juc同步框架
本文是个人对Doug Lea的The java.util.concurrent Synchronizer Framework一文的简单理解。
J2SE 1.5引入了java.util.concurrent(juc)包,其中包含了一个小的基于AbstractQueuedSynchronizer的同步框架,大部分juc内的同步器(Synchronizers)都基于该框架实现。
同步器需要包含两种类型的方法:
- 至少一个acquire方法,用于在同步状态允许线程运行之前阻塞组程;
- 至少一个release方法,用于更改同步状态使得其它线程从阻塞状态恢复;
java.util.concurrent包含多种类型的同步器,每种同步器都支持以下特性:
- 非阻塞同步和阻塞同步
- 同步等待超时
- 可取消(通过中断机制)
同步器可能仅管理独占(exclusive,同一时刻一个阻塞点只有一个线程可以通过)状态,或者也可能管理共享(share,一个阻塞点可能有多个线程通过)状态。
同步器的基本思想很简单,acquire操作:
while (synchronization state does not allow acquire) {
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;
release操作:
update synchronization state;
if (state may permit a blocked thread to acquire)
unblock one or more queued threads;
支持上述操作需要以下三个组件:
- 原子化管理的同步状态(synchronization state)
- 线程阻塞和取消阻塞(Blocking and unblocking threads)
- 维护队列(Maintaining queues)
同步状态
AbstractQueuedSynchronizer使用一个32位的整形变量state维护同步状态,并且暴露出getStae, setState, compareAndSetState方法来存取和更新状态。其中读和写操作是通过volatile语义,compareAndSetState则是通过硬件的compare-and-swap 或 loadlinked/store-conditional指令来保持操作的原子性。
阻塞
在JSR166出现之前,如果不采用内置的monitor机制,没有合适的用于同步器的阻塞和取消阻塞线程的API。具备此功能的两个方法Thread.suspend和Thread.resume由于其自身存在的不可解决的竞争问题而被标记为Deprecated了。java.util.concurrent.locks包引入了LockSupport类,通过LockSupport.park阻塞线程,LockSupport.unpark取消阻塞线程,这两者都是JVM通过本地机制实现的。
维护队列
维护队列是先进先出的非阻塞队列,两个主要的候选者是MCS locks和CLH locks,juc采用的是CLH locks,CLH通常会做为自旋锁(spinlock),不过也可被同步框架所采用,因为其可以很容易的进行取消和超时操作。
CLH队列如下图所示:
入队是一个原子操作
do { pred = tail;} while(!tail.compareAndSet(pred, node));
每一个节点的释放状态保存在其前驱节点内,”自旋锁“的”自旋“表现为如下式
while (pred.status != RELEASED) ; // spin
经过“自旋”后,出队操作仅仅是将头节点指向刚刚获得锁的节点
head = node;
CLH locks的好处是出队和入队都很快,无锁,并且无阻碍。检测是否有线程在等待也很快。
AbstractQueuedSynchronizer队列原始的CLH locks做了改造:
- 在节点内增加后继域
- 每个Node里使用状态域控制阻塞,而非自旋
- 节点的回收依赖GC
还有一些细节的变化,比如头节点的延迟初始化等,看AbstractQueuedSynchronizer的源码可以比较清楚的看到这些。
不考虑以上细节,acquire操作的通用形式如下:
if (!tryAcquire(arg)) {
node = create and enqueue new node;
pred = node's effective predecessor;
while (pred is not head node || !tryAcquire(arg)) {
if (pred's signal bit is set)
park();
else
compareAndSet pred's signal bit to true;
pred = node's effective predecessor;
}
head = node;
}
release操作的形式如下:
if (tryRelease(arg) && head node's signal bit is set) {
compareAndSet head's signal bit to false;
unpark head's successor, if one exists
}
AbstractQueuedSynchronizer框架的使用
一个Mutex的例子,使用状态0代表锁可用,状态1代表锁被占用:
class Mutex {
class Sync extends AbstractQueuedSynchronizer {
public boolean tryAcquire(int ignore) {
return compareAndSetState(0, 1);
}
public boolean tryRelease(int ignore) {
setState(0);
return true;
}
}
private final Sync sync = new Sync();
public void lock() { sync.acquire(0); }
public void unlock() { sync.release(0); }
}


