java.util.Concurrent.CountDownLatch 源码

类图

源码: 

package java.util.concurrent;

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

//在初始化时,初始化一个计数值.每执行countDown(),计数值减1;
//其他线程,可执行await()等待计数值为0才被唤醒继续执行
public class CountDownLatch {

    private final Sync sync;

    //继承AQS的内部类  
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;//版本号

        Sync(int count) {//设置状态的计数值
            setState(count);
        }

        int getCount() {//得到状态的计数值
            return getState();
        }

        protected int tryAcquireShared(int acquires) {//返回状态值是否为0
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {//释放初始化的持有的锁资源
            for (;;) {
                int c = getState();
                if (c == 0)//一开始初始化的锁资源,统统被释放了
                    return false;//返回false
                int nextc = c-1;//计数值减1
                if (compareAndSetState(c, nextc))//比较和更新计数值
                    return nextc == 0;//返回初始化持有的锁资源是否已被全部释放
            }
        }
    }

    //唯一的构造函数,初始化计数值    
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

    //执行AQS的可中断共享锁的方法
    public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }

    //执行AQS在指定时间内获取可中断共享锁的方法
    public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }

    //执行tryReleaseShared(),若计数值为0.为0则什么也不做;不为0则计数值减1,将sync队列去除节点
    public void countDown() {
        sync.releaseShared(1);
    }

    //得到当前计数值
    public long getCount() {
        return sync.getCount();
    }

    //返回类名+'@'+hashcode+'[Count ='+计数值+']'
    public String toString() {
        return super.toString() + "[Count = " + sync.getCount() + "]";
    }
}

类 CountDownLatch

    extends Object

    一个同步辅助类:某线程调用await() 之后将会一直等待,直到该组正在执行的线程的全部都执行完毕。

    用给定的计数 初始化 CountDownLatch

    由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。

    之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。

    计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier

 

构造方法摘要
CountDownLatch(int count) 
          构造一个用给定计数初始化的 CountDownLatch
方法摘要
 voidawait() 
          使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断
 booleanawait(long timeout, TimeUnit unit) 
          使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。
 voidcountDown() 
          递减锁存器的计数,如果计数到达零,则释放所有等待的线程。
 longgetCount() 
          返回当前计数。
 StringtoString() 
          返回标识此锁存器及其状态的字符串。

 

实现原理:

    await():当前线程在锁存器倒计数至零之前一直等待,除非线程被中断

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

    AQS:acquireSharedInterruptibly(int arg)

public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
    if (Thread.interrupted())//判断是否发生中断
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)//注意:-1表示获取到了共享锁,1表示没有获取共享锁
        doAcquireSharedInterruptibly(arg);
    }

    CountDownLatch 内部类Sync:int tryAcquireShared(int acquires)

protected int tryAcquireShared(int acquires) {
   return (getState() == 0) ? 1 : -1;//这里的state就是最开始new CountDownLatch(int count),count等于state
}

    如果获取共享锁继续调用doAcquireSharedInterruptibly(arg): 

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);//判断尝试获取锁
                    /*
                        这里要注意一下r的值就2种情况-1和1:
                        情况1.r为-1,latch没有调用countDown(),state是没有变化的导致state一直大于0或者调用了countDown(),但是state不等于0,直接在for循环中等待
                        情况2.r为1,证明countDown(),已经减到0,当前线程还在队列中,state已经等于0了.接下来就是唤醒队列中的节点 
                    */
                    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);
        }
    }

    如果这里多个线程wait之间没有调用countDown(),线程都在等待。如下图:

private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // 记录头结点
        setHead(node);//设置node为头结点
        /*
           *从上面传递过来 propagate = 1;
           *一定会进入下面的判断
        */
        if (propagate > 0 || h == null || h.waitStatus < 0) {
            Node s = node.next;//获得当前节点的下一个节点,如果为最后一个节点或者,为shared
            if (s == null || s.isShared())
                doReleaseShared();//释放共享锁
        }
}

    isShared(): 

final boolean isShared() {
    return nextWaiter == SHARED;
}

    释放共享锁,通知后面的节点:

private void doReleaseShared() {
        for (;;) {
            Node h = head;//获得头结点
            if (h != null && h != tail) {
                int ws = h.waitStatus;//获取头结点的状态默认值为0
                if (ws == Node.SIGNAL) {如果等于SIGNAL唤醒状态
                    //将头结点的状态置成0,并使用Node.SIGNAL(-1)与0比较,continue,h的状态设置为0,不会再进入if (ws == Node.SIGNAL)
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h);
                }//判断ws是否为0,并且h的状态不等于0,这里是个坑啊,ws等于0,h就是0啊,所以if进不来的,并设置节点为PROPAGATE
                else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            如果头结点等于h,其实也没有一直loop,由于上面写的Node h = head,就算前面的条件都不满足,这里一定会break
            if (h == head)                   // loop if head changed
                break;
        }
}

    compareAndSetWaitStatus(Node,int,int): 

private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);//使用JNI调用c++代码
}

 
    await(long timeout, TimeUnit unit):当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间

public boolean await(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (Thread.interrupted())//检测到中断,则抛出异常
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);//如果当前计数值为0,则直接返回true
}

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
        throws InterruptedException {
    if (nanosTimeout <= 0L)//参数时间小于等于0,直接返回false
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;//得到截至时间
    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 true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();//得到当前剩余时间
            if (nanosTimeout <= 0L)//剩余时间小于等于0,则返回false
                return false;
            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)//判断是否应该挂起当前节点的线程
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())//恢复之后,立即判断是否是由于中断导致的唤醒,是则抛出异常。
                throw new InterruptedException();
        }
    } finally {
        if (failed)//抛出异常,则执行清理操作,将当前节点等待状态置为取消状态
            cancelAcquire(node);
    }
}

 

    countDown(): 递减锁存器的计数,如果计数到达零,则释放所有等待的线程

public void countDown() {
   sync.releaseShared(1);//每次释放一个
}

    boolean releaseShared(int arg):

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {//尝试释放共享锁
            doReleaseShared();//释放共享锁
            //由于state等于0,所以从队列中,从头结点一个接着一个退出(break),for循环等待,
            return true;
        }
        return false;
}

    tryReleaseShared(int):

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))//比较并设置state
              return nextc == 0; //等于0的时候,肯定还有一些Node在队列中,所以要调用doReleaseShared(),来清理
    }
}

 

  

CountDownLatch

public CountDownLatch(int count)

    构造一个用给定计数初始化的 CountDownLatch

    参数:

    count - 在线程能通过 await() 之前,必须调用 countDown() 的次数

    抛出:

    IllegalArgumentException - 如果 count 为负

await

public void await() throws InterruptedException

    使当前线程在锁存器倒计数至零之前一直等待,除非线程被 中断

    如果当前计数为零,则此方法立即返回。

    如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下两种情况之一前,该线程将一直处于休眠状态:

  • 由于调用 countDown() 方法,计数到达零;或者
  • 其他某个线程中断当前线程。

    如果当前线程:

  • 在进入此方法时已经设置了该线程的中断状态;或者
  • 在等待时被中断

    则抛出 InterruptedException,并且清除当前线程的已中断状态。

    抛出:

    InterruptedException - 如果当前线程在等待时被中断

 

await

public boolean await(long timeout, TimeUnit unit) throws InterruptedException

    使当前线程在锁存器倒计数至零之前一直等待,除非线程被 中断或超出了指定的等待时间。

    如果当前计数为零,则此方法立刻返回 true 值。

    如果当前计数大于零,则出于线程调度目的,将禁用当前线程,且在发生以下三种情况之一前,该线程将一直处于休眠状态:

  • 由于调用 countDown() 方法,计数到达零;或者
  • 其他某个线程中断当前线程;或者
  • 已超出指定的等待时间。

    如果计数到达零,则该方法返回 true 值。

    如果当前线程:

  • 在进入此方法时已经设置了该线程的中断状态;或者
  • 在等待时被中断

    则抛出 InterruptedException,并且清除当前线程的已中断状态。

    如果超出了指定的等待时间,则返回值为 false。如果该时间小于等于零,则此方法根本不会等待。

    参数:

    timeout - 要等待的最长时间

    unit - timeout 参数的时间单位。

    返回:

        如果计数到达零,则返回 true;如果在计数到达零之前超过了等待时间,则返回 false

    抛出:

    InterruptedException - 如果当前线程在等待时被中断

 

countDown

public void countDown()

    递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

    如果当前计数大于零,则将计数减少。如果新的计数为零,出于线程调度目的,将重新启用所有的等待线程。

    如果当前计数等于零,则不发生任何操作。

 

getCount

public long getCount()

    返回当前计数。

    此方法通常用于调试和测试。

    返回:

        当前计数

 

toString

public String toString()

    返回标识此锁存器及其状态的字符串。状态用括号括起来,包括字符串 "Count =",后跟当前计数。

    覆盖:

        类 Object 中的 toString

    返回:

        标识此锁存器及其状态的字符串

使用示例:
  • 1.main线程只有CountDownLatch的计数器为0时,才会继续执行
package com.thread;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest1 extends Thread {
    private static int LATCH_SIZE = 5;
    private static CountDownLatch doneSignal;

    public void run() {
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " 休眠片刻.");
            //将CountDownLatch的数值减1
            doneSignal.countDown();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " 即将执行结束.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            doneSignal = new CountDownLatch(LATCH_SIZE);
            // 新建5个任务
            for (int i = 0; i < LATCH_SIZE; i++)
                new CountDownLatchTest1().start();
            System.out.println("main线程即将进入await");
            // "主线程"等待线程池中5个任务的完成
            doneSignal.await();
            System.out.println("main线程继续执行.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    运行结果:

main线程即将进入await
Thread-2 休眠片刻.
Thread-1 休眠片刻.
Thread-4 休眠片刻.
Thread-0 休眠片刻.
Thread-3 休眠片刻.
main线程继续执行.
Thread-1 即将执行结束.
Thread-0 即将执行结束.
Thread-4 即将执行结束.
Thread-2 即将执行结束.
Thread-3 即将执行结束.

  • 2.执行多次countDown()的运行结果:
package com.thread;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest1 extends Thread {
    private static int LATCH_SIZE = 5;
    private static CountDownLatch doneSignal;

    public void run() {
        try {
            Thread.sleep(3000);
            System.out.println(Thread.currentThread().getName() + " 休眠片刻.");
            //将CountDownLatch的数值减1
            doneSignal.countDown();
            doneSignal.countDown();
            doneSignal.countDown();
            Thread.sleep(1000);
            System.out.println(Thread.currentThread().getName() + " 即将执行结束.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            doneSignal = new CountDownLatch(LATCH_SIZE);
            // 新建5个任务
            for (int i = 0; i < LATCH_SIZE; i++)
                new CountDownLatchTest1().start();
            System.out.println("main线程即将进入await");
            // "主线程"等待线程池中5个任务的完成
            doneSignal.await();
            System.out.println("main线程继续执行.");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    运行结果:

main线程即将进入await
Thread-2 休眠片刻.
Thread-0 休眠片刻.
main线程继续执行.
Thread-3 休眠片刻.
Thread-4 休眠片刻.
Thread-1 休眠片刻.

Thread-0 即将执行结束.
Thread-2 即将执行结束.
Thread-3 即将执行结束.
Thread-4 即将执行结束.
Thread-1 即将执行结束.

    多次执行 countDown() 可能会导致 mian线程提前唤醒。因为执行countDown()会直接将计数减1,导致部分线程还没执行countDown(),计数值就变成了0。当计数值已经变为0时,main线程就被提前唤醒,此时其他线程执行countDown(),根据源码可以发现,此时将什么也不做。

转载于:https://my.oschina.net/langwanghuangshifu/blog/2881359

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值