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的注释已经很详尽了。


最后简单总结一下。按照个人的理解。模板方法就是在一个流程从具体的某一步骤需要由具体的情况决定。但是其他的步骤都是一致的。

所以可以定义抽象父类实现通用的步骤,由子类去实现专用的步骤。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值