多线程工具类-CountDownLatch


CountDownLatch

0、基础

  • countDownLatch是在java1.5被引入,跟它一起被引入的工具类还有CyclicBarrier、Semaphore、concurrentHashMap和BlockingQueue。

  • 存在于java.util.cucurrent包下。

  • countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。

  • 是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。


1、初始化CountDownLatch

final CountDownLatch countDownLatch = new CountDownLatch(3);
这里的3 是初始化的数字
观察源码,可以知道这个是通过修改volatile关键字来实现的

public CountDownLatch(int count) {
    if (count < 0) throw new IllegalArgumentException("count < 0");
    this.sync = new Sync(count);
}

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

        protected final void setState(int newState) {
            state = newState;
        }

        private volatile int state;

2、常用方法

2.1、countDownLatch.await();

阻塞线程,知道共享变量state为0继续执行代码

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);
    }
		
		//只要初始值的state不为0就返回-1,也就是满足if条件小于0
        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected final int getState() {
            return state;
        }

        private void doAcquireSharedInterruptibly(int arg)
            throws InterruptedException {
            final Node node = addWaiter(Node.SHARED);
            boolean failed = true;
            try {
                //相当于while(true) 使用中断来打断死循环
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head) {
                    //获取volatile共享变量state的值判断   一旦state为0 这里r为1中断循环
                        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);
            }
        }

2.2、countDownLatch.countDown();

原理cas方式去让state--,

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

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

        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;
                //cas方式去让state-1
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }

private void doReleaseShared() {
//确保发布传播,即使还有其他正在进行的获取发布。如果需要信号,这会以通常的方式尝试 unparkSuccessor 的 head。但如果没有,则将状态设置为 PROPAGATE 以确保在发布时继续传播。此外,我们必须循环以防在我们执行此操作时添加了新节点。此外,与 unparkSuccessor 的其他用法不同,我们需要知道 CAS 重置状态是否失败,如果是则重新检查。
        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;
        }
    }

2.3、getCount()

获取当前的state


3、实战

初始化CountDownLatch,初始值为3,首先启动t2,t3线程,在执行System.out.println后执行countDownLatch.await();,进入阻塞状态,Thread.sleep(2000);是确保t2,t3进入阻塞,然后将countDownLatch.countDown,知道减到0,这样之前阻塞的线程会继续执行后续的代码。

public static void main(String[] args) {

//通过构造方法传入初始值   底层是 volatile
      final CountDownLatch countDownLatch = new CountDownLatch(3);

      new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  System.out.println("t2 start waitting for countDownLatch to zero");
                  countDownLatch.await();
                  System.out.println("t2 stop");
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }, "t2").start();

      new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  System.out.println("t3 start waitting for countDownLatch to zero");
                  countDownLatch.await();
                  System.out.println("t3 stop");
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
          }
      }, "t3").start();

      try {
          Thread.sleep(2000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }

      new Thread(new Runnable() {
          @Override
          public void run() {
              try {
                  Thread.sleep(1000);
                  System.out.println("countDownLatch.countDown1");
                  countDownLatch.countDown(); //3-1=2
                  Thread.sleep(1000);
                  System.out.println("countDownLatch.countDown2");
                  countDownLatch.countDown(); //2-1=1
                  Thread.sleep(1000);
                  System.out.println("countDownLatch.countDown3");
                  countDownLatch.countDown();    //1-1=0
              } catch (Exception e) {
                  e.printStackTrace();
              }
          }
      }, "t1").start();
  }

控制台输出

image-20220222163838535