Java 并发 CountLatchDown

来源:互联网 发布:linux 新建文件 编辑:程序博客网 时间:2024/06/06 01:16

一个同步工具,使得当前线程等待,直到其他多个线程完成一系列的操作后,继续执行。
其中的当前线程也可以是多个线程,这个地方的线程的数量是任意的。

也就是await() 可以在多个线程中调用。
这个方法可以使得当前线程等待,也就是当前线程在未获得同步状态时,进入等待状态。因此这个这个也是获取同步状态的方法。这个方法获取同步状态,是以共享式的获取同步状态,因此await() 可以在多个线程中调用。

CountLatchDown的实现也是基于AbstractQueuedSynchronizer 同步器。

1 状态 state
state在CountDownLatch 起到一个计数器的作用,可以表示需要等待线程的数量。
当state==0时,await() 返回,然后继续执行后面的代码。当创建CountDownLatch时,初始化state的数量。

2 countDown 方法

public void countDown() {        sync.releaseShared(1);    }

每调用一次,同步器就会共享式的释放一个拥有同步状态的线程。state也相应的减一。

public final boolean releaseShared(int arg) {        if (tryReleaseShared(arg)) {            doReleaseShared();            return true;        }        return false;    }

这个地方就使用了模板设计模式。上面这个方法是在同步器(父类)中定义的。

tryReleaseShared在不同的同步组件中有不同的实现,在CountDownLatch的实现如下:

 protected boolean tryReleaseShared(int releases) {            // Decrement count; signal when transition to zero            for (;;) {                int c = getState();                if (c == 0)                    return false;                int nextc = c-1;                if (compareAndSetState(c, nextc))                    return nextc == 0;            }        }

逻辑就是无限循环的尝试将 状态以线程安全的方式减一。

3 await方法

 public void await() throws InterruptedException {        sync.acquireSharedInterruptibly(1);    }

共享式的获取同步状态。

  public final void acquireSharedInterruptibly(int arg)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);    }

这个方法也是定义在同步器(父类)中。

tryAcquireShared 共享式的获取同步状态,大于0时,表示获取成功。

  if (tryAcquireShared(arg) < 0)            doAcquireSharedInterruptibly(arg);

表示未成功获取同步状态时,进入方法doAcquireSharedInterruptibly

private void doAcquireSharedInterruptibly(int arg)        throws InterruptedException {        //创建共享节点 并加入队列        final Node node = addWaiter(Node.SHARED);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head) {                    int r = tryAcquireShared(arg);                    if (r >= 0) {                        setHeadAndPropagate(node, r);                        p.next = null; // help GC                        failed = false;                        return;                    }                }                if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    throw new InterruptedException();            }        } finally {            if (failed)                cancelAcquire(node);        }    }

//创建共享节点 并加入队列

final Node node = addWaiter(Node.SHARED);  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;    }

先看下 Node的构造器:

   Node(Thread thread, Node mode) {     // Used by addWaiter            this.nextWaiter = mode;            this.thread = thread;        }

也就是Node.SHARED 赋值给了 this.nextWaiter。

介绍下addWaiter的实现逻辑:

当队尾节点不为null时,尝试设置为队尾节点,有可能失败。如果成功直接返回。

失败进入enq方法:

  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;                }            }        }    }

当同步对列中没有任何结点时,

首先看下这个compareAndSetHead(new Node())这个方法,
这个方法一开始看的时候,没有看仔细,把 参数里面的new Node() 当成了前面传过来的node,导致一直不理解这个“操作”。后来突然发现这个是 新建的空节点 。

/**     * CAS head field. Used only by enq.     */    private final boolean compareAndSetHead(Node update) {        return unsafe.compareAndSwapObject(this, headOffset, null, update);    }

设置head节点为空节点,tail=head。

后面这个方法compareAndSetTail 就没有问题了,就是以原子的方式追加到同步队列的末尾。

至此 final Node node = addWaiter(Node.SHARED);
执行完毕。

继续看下面的代码,下面是尝试获取同步状态的代码。 先看下,同步状态获取不成功的情况(一开始时,一般情况下,是不会马上就会获得同步状态的。)

if (shouldParkAfterFailedAcquire(p, node) &&                    parkAndCheckInterrupt())                    throw new InterruptedException();初始情况下,  waitStatus 为0;private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {        int ws = pred.waitStatus;        if (ws == Node.SIGNAL)            /*             * This node has already set status asking a release             * to signal it, so it can safely park.             */            return true;        if (ws > 0) {            /*             * Predecessor was cancelled. Skip over predecessors and             * indicate retry.             */            do {                node.prev = pred = pred.prev;            } while (pred.waitStatus > 0);            pred.next = node;        } else {            /*             * waitStatus must be 0 or PROPAGATE.  Indicate that we             * need a signal, but don't park yet.  Caller will need to             * retry to make sure it cannot acquire before parking.             */            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);        }        return false;    }

所以执行的是 compareAndSetWaitStatus
把节点设置为SIGNAL状态,然后这个状态的节点随后会被设置,阻塞
也就是这个方法parkAndCheckInterrupt。
LockSupport.park(this);

然后我们看获取到同步状态的情况:

这种情况,是在countdown 方法进行了足够的执行之后,state==0时

final Node p = node.predecessor();返回的是前序节点。final Node predecessor() throws NullPointerException {            Node p = prev;            if (p == null)                throw new NullPointerException();            else                return p;        }如果前序节点为 head节点。尝试获取同步状态。if (p == head) {                    int r = tryAcquireShared(arg);                    if (r >= 0) {//获取成功                        setHeadAndPropagate(node, r);                        p.next = null; // help GC                        failed = false;                        return;                    }                }
private void setHeadAndPropagate(Node node, int propagate) {        Node h = head; // Record old head for check below        setHead(node);        /*         * Try to signal next queued node if:         *   Propagation was indicated by caller,         *     or was recorded (as h.waitStatus) by a previous operation         *     (note: this uses sign-check of waitStatus because         *      PROPAGATE status may transition to SIGNAL.)         * and         *   The next node is waiting in shared mode,         *     or we don't know, because it appears null         *         * The conservatism in both of these checks may cause         * unnecessary wake-ups, but only when there are multiple         * racing acquires/releases, so most need signals now or soon         * anyway.         */        if (propagate > 0 || h == null || h.waitStatus < 0) {            Node s = node.next;            if (s == null || s.isShared())                doReleaseShared();        }    }/**     * Release action for shared mode -- signal successor and ensure     * propagation. (Note: For exclusive mode, release just amounts     * to calling unparkSuccessor of head if it needs signal.)     */    private void doReleaseShared() {        /*         * Ensure that a release propagates, even if there are other         * in-progress acquires/releases.  This proceeds in the usual         * way of trying to unparkSuccessor of head if it needs         * signal. But if it does not, status is set to PROPAGATE to         * ensure that upon release, propagation continues.         * Additionally, we must loop in case a new node is added         * while we are doing this. Also, unlike other uses of         * unparkSuccessor, we need to know if CAS to reset status         * fails, if so rechecking.         */        for (;;) {            Node h = head;            if (h != null && h != tail) {                int ws = h.waitStatus;                if (ws == Node.SIGNAL) {                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))                        continue;            // loop to recheck cases                    unparkSuccessor(h);                }                else if (ws == 0 &&                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))                    continue;                // loop on failed CAS            }            if (h == head)                   // loop if head changed                break;        }    }

因为节点的状态是是signal,然后将这个状态设置为0 ,如果成功了,执行unparkSuccessor,: LockSupport.unpark(s.thread); 这个就是唤醒节点的方法,和前面的方法LockSupport.park(this); 是相对应的。

原创粉丝点击