面试遇到CountDownLatch有这一篇就够啦

前言

日常开发中多线程是我们经常用到了一种技术手段,通过合理的使用多线程可以极大的提高程序中的处理能力,但是在使用多线程的过程中,我们一定需要特别关注多个线程在处理过程中对后续任务的影响,在处理那些涉及多线程任务和单线程任务需要顺序处理的时候,我们就可以通过CountDownLatch来进行控制,接下来我们就来深入了解一下CountDownLatch的原理和用法。

源码分析

  • 静态类:Sync
    初始化CountDownLatch便是创建了一个私有的静态类Sync,Sync继承了抽象类AbstractQueuedSynchronizer,初始化的过程主要是对标志量state进行赋值。在Sync中重写了tryAcquireShared、tryReleaseShared
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) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }
  • countDown:
    该方法是对我们赋值的标志量进行减1的操作,该线程是一个线程安全的操作,通过自旋来获取标志量state,并对state进行线程安全的uncafe.compareAndSwapInt()操作
public void countDown() {
        sync.releaseShared(1);
    }
  • await:
    该方法主要是用来判断上方赋值的标志量state是否已被减为0,当state为0时表示所有线程均执行完成,否则进行自旋状态来不断尝试判断是否为0,await方法还有一个重载方法,可以设置等待的最长时间,如果直到最大等待时间state还不为0,则退出自旋继续执行下面的代码。
public void await() throws InterruptedException {
        sync.acquireSharedInterruptibly(1);
    }
    
public boolean await(long timeout, TimeUnit unit)
        throws InterruptedException {
        return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }    
  • getCount:
    获取标志量state当前的值
public void countDown() {
        sync.releaseShared(1);
    }

使用场景

A、B两个任务,A任务通过多线程来进行处理,只有A任务的全部线程都执行完成了才可以执行B任务,这个时候就可以通过给A任务中加一个公用的CountDownLatch。
在这里插入图片描述

使用步骤

  • 1 创建CountDownLatch实例,初始化state的值
  • 2 创建线程池,在线程池中新建线程,初始化线程中传入CountDownLatch实例
  • 3 在新建线程中处理业务代码,处理完成则调用countDown方法来对CountDownLatch实例中的state的标志量减1
  • 4 调用CountDownLatch实例中的await方法,如果没有设置超时时间,则将阻塞当前线程直到标志量state = 0为止;如果设置了await方法的超时时间,在超时时间内标志量state被减为0则退出线程阻塞,在超时时间内标志量state未被减为0,到了超时时间也会退出线程阻塞
  • 5 接下来将执行await方法后的逻辑

主线程

public class TestCountDownLatch {

  public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(2);
    CountDownLatch countDownLatch = new CountDownLatch(10);
    for (int i = 0; i < 10; i++) {
      executorService.execute(new HaveCountDownLatchThread(countDownLatch, i, new Random()));
    }
    try {
      countDownLatch.await();

      System.out.println("查询当前状态量state : " +countDownLatch.getCount() + "; 前面的线程已经全部执行完成,可以接着冲啦。。。");
    } catch (InterruptedException e) {
      e.printStackTrace();
    }


  }
}

任务线程

public class HaveCountDownLatchThread implements Runnable{
  CountDownLatch countDownLatch;
  int i;
  Random random;

  HaveCountDownLatchThread(CountDownLatch countDownLatch, int i, Random random) {
    this.countDownLatch = countDownLatch;
    this.i = i;
    this.random = random;
  }
  @Override
  public void run() {
    int sleep = random.nextInt(1000);
    System.out.println("第【" + i + "】个启动的线程即将睡眠 " + sleep + "毫秒");
    try {
      Thread.sleep(sleep);
      System.out.println("第【" + i + "】个启动的线程已睡醒,查询当前的state状态量:" + countDownLatch.getCount());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("第【" + i + "】个启动的线程已睡醒,即将执行countDown方法");
    countDownLatch.countDown();

  }
}

总结

CountDownLatch是Java中concurrent包中的一个工具类,CountDownLatch可以看作是一个计数器,这个计数器的操作是原子操作:同一时刻只能有一个线程去操作这个计数器,我们初始化CountDownLatch的时候设置一个计数值,计数器中的计数值被线程减为0之前,任何调用CountDownLatch对象上的await方法都会被阻塞,任何线程调用CountDownLatch对象中的countDown方法则为计数器减1。一直减到state==0或者超时后,开始执行计数器的await方法后的代码,所以如果我们将CountDownLatch的源码查看一下,你会发现,CountDownLatch并没有那么复杂,加油!

猜你喜欢

转载自blog.csdn.net/zhangzehai2234/article/details/105986056