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的註釋已經很詳盡了。
最後簡單總結一下。按照個人的理解。模板方法就是在一個流程從具體的某一步驟需要由具體的情況決定。但是其他的步驟都是一致的。
所以可以定義抽象父類實現通用的步驟,由子類去實現專用的步驟。