java并发--CountDownLatch简单分析

CountDownLatch在java并发编程实战一书中翻译为闭锁。主要的作用是一组线程等待另一组线程完成事件后调用 CountDownLatch.countDown 方法,当里面的状态 为0的时候,其他调用CountDownLatch.await方法的线程继续往下执行。

首先看下构造方法
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count);
    }

首先判断参数再调用的都是Sync的构造方法。

    Sync(int permits) {
            setState(permits);
        }

把你实例化的count设置给AQS的state参数,代表你要调多少次countDown方法,await才会返回。

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





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

CountDownLatch直接把调用转到Sync里,Sync首先判断当前线程是否被中断,再调用tryAcquireShared,tryAcquireShared的实现在Sync里

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

Sync.tryAcquireShared是判断State是否等于0,前面在构造方法那里已经说过,实例化的时候回给State设置值,当CountDownLatch还未被调用countDown方法的时候,这里返回的是-1,接着就进入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);
        }
    }

doAcquireSharedInterruptibly的实现是在AQS里,java很多并发组件都会调用这个方法,像信号量,读写锁之类的。这个方法的作用如下

  1. 给当前线程创建NODE节点,并标记为共享锁,在把NODE节点设置在CLH的队尾
  2. 获取当前线程的前继节点,判断前继节点是否是头节点,是头结点的话就去尝试获取锁,
  3. 调用shouldParkAfterFailedAcquire,判断当前节点是否需要被唤醒,唤醒的话就放回true,否则就移除CLH中被取消的NODE,返回false
  4. 调用parkAndCheckInterrupt方法,阻塞当前线程
    
    
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL)
               
                return true;
            if (ws > 0) {
              
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
               
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }
    
    private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }

     

await方法大致就是这些,还有些细节,并不影响,我这就不展开了

countDown

    public void countDown() {
        sync.releaseShared(1);
    }
也是直接转给Sync,具体实现实在AQS里
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
首先尝试释放共享锁,调用成功的话在调用doReleaseShared方法释放共享锁。
tryReleaseShared是在CountDownLatch.Sync里重写了
    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;
            }
        }
主要的功能就是把锁的state - 1,获取成功后就会调用  doReleaseShared方法
 private void doReleaseShared() {
       
        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;
        }
    }
  1. 首先会判断 head的状态是不是SIGNAL,是SIGNAL的话就尝试调用unparkSuccessor将状态复位;即把waitStatus设置为0
  2.  设置成功,则唤醒下一节点,否则继续循环。 
  3. 如果waitStatus==0 ,就尝试把waitStatus设置为PROPAGATE,
  4. 头结点没变化的话就跳出循环,否则继续循环

下面贴一下unparkSuccessor的代码

    private void unparkSuccessor(Node node) {
      
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

主要就是状态判断,唤醒下一线程

 

总结一下

CountDownLatch是一共享锁的形式实现的等待/通知功能,功能就是:  一组线程等待另一组线程完成事件后调用 CountDownLatch.countDown 方法,当里面的状态 为0的时候,其他调用CountDownLatch.await方法的线程继续往下执行。和CyclicBarrier可以重复使用不同,CountDownLatch当状态为0了,就不能变成初始化在使用了。

主要就是两方法,主线程调用Await方法等待其他线程完成任务后调用 countDown方法唤醒。

调用Await方法时有两种情况

  1. 获取锁成功 即 state ==0,从第一个节点开始,依次唤醒所有线程
  2. 获取锁失败,把当前线程制作成NODE,标记为共享锁,放在CLH的队尾

调用countDown 方法时也有两种情况

  1. state=0,就唤醒节点
  2. state!=0,把state-1,继续阻塞
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,您想了解关于Java并发编程中的CountDownLatch吗? CountDownLatchJava并发编程中非常重要的一个工具类,它可以让某个线程等待其他线程完成后再继续执行。它的作用类似于倒计时门闩,当count值减为0时,所有等待者都会被唤醒。 CountDownLatch是一次性的,也就是说,它的计数器只能使用一次。如果需要重复使用,可以考虑使用CyclicBarrier。 在使用CountDownLatch时,我们需要先创建一个CountDownLatch对象,并指定计数器的初始值。然后在需要等待的线程中调用await()方法进行等待,同时在其他线程中调用countDown()方法进行计数器的减1操作。 举个例子,假设我们有一个需求:主线程需要等待两个子线程完成后再执行。那么可以这样编写代码: ```java import java.util.concurrent.CountDownLatch; public class CountDownLatchDemo { public static void main(String[] args) throws InterruptedException { CountDownLatch countDownLatch = new CountDownLatch(2); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行开始"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); countDownLatch.countDown(); }, "线程1").start(); new Thread(() -> { System.out.println(Thread.currentThread().getName() + "执行开始"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "执行完毕"); countDownLatch.countDown(); }, "线程2").start(); System.out.println(Thread.currentThread().getName() + "等待子线程执行完毕"); countDownLatch.await(); System.out.println(Thread.currentThread().getName() + "所有子线程执行完毕,继续执行主线程"); } } ``` 在上面的例子中,我们首先创建了一个计数器初始值为2的CountDownLatch对象,然后创建了两个线程分别进行一些操作,并在操作结束后调用countDown()方法进行计数器减1操作。在主线程中,我们调用await()方法进行等待,直到计数器减为0时,主线程才会继续执行。 希望能够对您有所帮助!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值