1. 程式人生 > >jdk遇到設計模式之模板方法

jdk遇到設計模式之模板方法

突然想寫一個系列,記錄在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的註釋已經很詳盡了。

最後簡單總結一下。按照個人的理解。模板方法就是在一個流程從具體的某一步驟需要由具體的情況決定。但是其他的步驟都是一致的。

所以可以定義抽象父類實現通用的步驟,由子類去實現專用的步驟。