首页 > 编程语言 > java并发包中CountDownLatch和线程池的使用详解
2021
02-21

java并发包中CountDownLatch和线程池的使用详解

1.CountDownLatch

现在做的这个华为云TaurusDB比赛中,参考的之前参加过阿里的PolarDB大赛的两个大佬的代码,发现都有用到CountDownLatch这个类,之前看代码的时候也看过,但是没有搞得很明白,自己写也写不出来,在此自己先学习一下。

字面理解:CountDownLatch:数量减少的门栓。

创建这样一个门栓

CountDownLatch countDownLatch = new CountDownLatch(count);

参数:count,门栓的计数次数。

在所有线程执行完成之前,调用countDownLatch.await()阻塞主线程。

每当一个线程执行完一个指定动作之后,count就会减少1,当count等于0时,主线程不再阻塞,开始继续执行下面的代码,当count大于0时,主线程一直阻塞,等待count变为0。每个线程动作执行结束后,执行countDownLatch.countDown(),这个门栓的count减一。

int ThreadNum = 16;
CountDownLatch countDownLatch = new CountDownLatch(ThreadNum);
for(int i = 0; i < ThreadNum ; i++){
 final int finalI = i;
 new Thread(() -> {
  int n = 0;
  System.out.println("线程应该做的事情");
  while(n < 10){
   n++;
  }
  countDownLatch.countDown();
 }).start();
}
try{
 countDownLatch.await();
}catch(InterruptedException e){
 logger.infor("InterruptedException!!");
}

2.线程池

其实线程池之前的ipv6的项目里用过,但是也忘记得差不多了,复习一下。

线程在创建和关闭时都需要花费时间,如果为每一个小的任务都创建一个线程,可能创建和销毁线程所用的时间会多于该线程真实工作所消耗的时间,就会得不偿失。除了时间,空间也需要考虑,线程本身也是要占用内存空间的,大量的线程会食用过多的内存资源,可能会造成OOM。另外在回收时,大量的线程会延长GC的停顿时间。

因此在生产环境中使用线程必须对其加以控制和管理

使用线程池之后,创建线程变成了从线程池中获得空闲的线程,关闭线程变成了归还线程给线程池。

通过ThreadPoolExecutor可以创建一个线程池,ThreadPoolExecutor实现了Executors接口。

举个栗子:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolTest {
 public static void main(String[] args) {
  ThreadPoolExecutor pool = new ThreadPoolExecutor(10,20,60, 
    TimeUnit.SECOUNDS,new ArrayBlockingQueue<Runnable>(15000),new ThreadFactory(){
   private AtomicInteger threadId = new AtomicInteger(0);
   @Override
   public Thread newThread(Runnable r){
    Thread thread = new Thread(r);
    thread.setDaemon(true);
    String prefix = "thread-";
    thread.setName(prefix+threadId.incrementAndGet());
    return thread;
   } 
  });
 }
}

这样就创建了一个线程池。参数依次解释:

corePoolSize:指定了线程池中线程的数量,线程池中可以有10个存活的线程

maximumPoolSize:指定了线程池中最大的线程数,线程池中最多能有20个存活的线程

keepAliveTime:当线程池中的数量超过corePoolSize时,这些线程在多长时间会被销毁,60s

unit:keepAliveTime的单位

workQueue:任务队列,被提交但是没有被执行的任务存在的地方。他是一个BlockingQueue<Runnable>接口的对象。

threadFactory:线程工厂,你想创建什么样子的线程

重点说一下workQueue:

根据队列的功能分类,可以使用以下几种BlockingQueue接口

补充:Java中CountDownLatch,CyclicBarrier以及Semaphore的使用场景

Java并发包中提供了很多有用的工具类来帮助开发者进行并发编程,今天我就来说说CountDownLatch,CyclicBarrier以及Semaphore这三个的用法和使用场景。

1.CountDownLatch使用场景和用法

CountDownLatch一般是用于某个线程等待其他线程执行完之后,它才能执行。例如一家人在等待爸爸妈妈回家,才能进行晚宴,示例代码如下:

public class CountDownLatchTest {
 
 public static void main(String[] args) throws Exception {
  final CountDownLatch cdl = new CountDownLatch(2);
  new Thread(){
   public void run() {
    try {
     System.out.println("等待老爸回家...");
     Thread.sleep(5000);
     cdl.countDown();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    
   };
  }.start();
  
  new Thread(){
   public void run() {
    try {
     System.out.println("等待老妈回家...");
     Thread.sleep(5000);
     cdl.countDown();
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
   };
  }.start();
  
  cdl.await();
  System.out.println("老爸老妈回来了...");
  System.out.println("晚宴开始了...");
 }
 
}

2.CyclicBarrier(栅栏)使用场景和用法

CyclicBarrier一般是一组线程等待至某个状态,然后这一组线程才能同时执行(感觉跟CountDownLatch有点类似啊,不过仔细想想还是有差别的,感觉容易混淆)。

代码示例如下:

public class CyclicBarrierTest {
 
 public static void main(String[] args) {
  int count = 3;
  CyclicBarrier cb = new CyclicBarrier(count, new Runnable() {
   @Override
   public void run() {
    //此处所有线程都调用了await方法之后,会走到这里
    System.out.println("所有线程操作完成之后都调用了await方法");
   }
  });
  
  for(int i=0;i<count;i++){
   new WriteLogHandler(cb).start();
  }
 }
 
 static class WriteLogHandler extends Thread{
  
  private CyclicBarrier cb = null;
  
  public WriteLogHandler(CyclicBarrier cb) {
   this.cb = cb;
  }
  
  @Override
  public void run() {
   try {
    System.out.println("线程:" + Thread.currentThread().getName() + "开始写日志");
    Thread.sleep(2000);
    System.out.println("线程:" + Thread.currentThread().getName() + "写日志结束,等待其他线程");
    cb.await();
    
    System.out.println("所有线程写日志数据结束,继续其他操作");
   } catch (Exception e) {
    e.printStackTrace();
   }
  }
 }
 
}

3.Semaphore(信号量)使用场景和用法

Semaphore类似锁的用法,用于控制对某资源的访问权限,示例代码如下:
public class SemaphoreTest {
 
 public static void main(String[] args) {
  ExecutorService executor = Executors.newCachedThreadPool();
  final Semaphore semaphore = new Semaphore(5);
  
  for(int i=0;i<10;i++){
   final int num = i;
   executor.execute(new Runnable() {
    @Override
    public void run() {
     try {
      semaphore.acquire();
      System.out.println("正在执行任务" + num);
      Thread.sleep((long)Math.random() * 1000);
      System.out.println("任务" + num + "执行结束");
      semaphore.release();
     } catch (Exception e) {
      e.printStackTrace();
     }
    }
   });
  }
  executor.shutdown();
 }
 
}

以上就是这三个并发工具类的使用场景和示例,仅为个人经验,希望能给大家一个参考,也希望大家多多支持自学编程网。如有错误或未考虑完全的地方,望不吝赐教。欢迎大家一起交流。

编程技巧