突然想写一个系列,记录在jdk中使用的设计模式。
1、jdk中有很多用到模板方法的地方,比如众所周知的当类comparable接口后可以用Collections.sort (和 Arrays.sort )。其实这就是一个经典的模板方法。
2、今天想详细的叙述另一个jdk中经典的模板方法的应用类AQS(AbstractQueuedSynchronizer),这是在java并发包中非常重要的一个类,比如ReetrantLock,ReadWriteLock等很多同步工具都是基于此类实现。甚至连jdk1.6之前的FutureTask都是基于此实现。(当然,由于通用和专用的天然矛盾,比如StampedLock就没有继承该抽象类)
根据记忆AQS有5个抽象方法(可能有误)需要子类去实现,
isHeldExclusively():该方法将返回同步对于当前线程是否是独占的(一般Condition需要去实现)
tryAcquire(int):独占方式。尝试获取资源。
tryRelease(int):独占方式。尝试释放资源。
tryAcquireShared(int):共享方式。尝试获取资源。
tryReleaseShared(int):共享方式。尝试释放资源。
今天的重点是模板方法,所以我们以tryAcquire(int)为例子来看看是AQS是如何应用模板方法的。
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
在acquire方法中我们看到了调用tryAcquire,上面这段代码的大意是。
先尝试tryAcquire获取资源,失败的话
1、将当前等待线程包装成一个等待节点加入等待队列(addWaiter()方法)
2、从队列中尝试获取临界资源(acquireQueued()方法)
接下来看看addWaiter方法的实现
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
试着把上面两个方法连起来看。
在addWaiter中先获取等待队列的尾节点,并且尝试设置新的尾节点。
cas失败或者tail是null的情况下,调用enq方法。
enq方法很简单,源码扫一眼就知道了。
通过上面的讲述,我们已经知道了addWaiter方法的实现。就是将等待节点插入等待队列中。
那么接下来看acquireQueued方法。
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
在循环中主要做了这些事情:
1、找到当前节点的前任节点
2、前任节点是头节点、并且tryAcquire成功
3、把自己设置成头节点
4、前任节点不是头节点
5、睡、或者不睡这个取决于约定的时间(acquire(time))
6、睡得时候顺便检查一下是否被打断了
finally中主要是failed就取消Acquire。
最后看一下cancelAcquire方法吧
/**
* Cancels an ongoing attempt to acquire.
*
* @param node the node
*/
private void cancelAcquire(Node node) {
//node为空
if (node == null)
return;
//将thread设置null
node.thread = null;
// Skip cancelled predecessors
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
Node predNext = pred.next;
// Can use unconditional write instead of CAS here.
// After this atomic step, other Nodes can skip past us.
// Before, we are free of interference from other threads.
node.waitStatus = Node.CANCELLED;
// If we are the tail, remove ourselves.
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// If successor needs signal, try to set pred's next-link
// so it will get one. Otherwise wake it up to propagate.
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
这个douglea的注释已经很详尽了。
最后简单总结一下。按照个人的理解。模板方法就是在一个流程从具体的某一步骤需要由具体的情况决定。但是其他的步骤都是一致的。
所以可以定义抽象父类实现通用的步骤,由子类去实现专用的步骤。