在java.util.concurrent包下面,Java为并发编程提供了三个简单却使用的三个工具类,分别是CountDownLatch、CyclicBarrier和Semaphore。接下来将简要的初步了解下这三个工具类的用途。
1、等待多线程完成的CountDownLatch
(1)作用:该工具类通过 减法倒计数 的方式来实现让一个或多个线程等待前面的一个或一组线程完成操作。
(2)方法:
- 构造方法:
public CountDownLatch(int count) {
if (count < 0) throw new IllegalArgumentException("count < 0");
this.sync = new Sync(count);
}
count代表前面需要等待的 多线程的个数 或者是 一个线程内的多个步骤数。
- 成员方法:该工具类主要有两个方法:
countDown()
: 当调用CountDownLatch的countDown方法时,count就会减1。await()
:CountDownLatch的await()方法会阻塞当前线程,直到count数为0时,JVM会唤醒该线程。
(3)使用:
【demo1】:当班上的5位同学都离开教室后,班长才锁上教室的门。
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 前面有5位同学需要离开教室
CountDownLatch countDownLatch = new CountDownLatch(5);
// 创建5个同学线程,分别执行离开教室的线程
for(int i=0;i<5;i++){
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 离开了教室");
// 离开了之后就将计数器减1
countDownLatch.countDown();
},String.valueOf(i)).start();
}
// 调用await()方法,使得班长线程在同学线程还没有离开之前一直处于阻塞,当count为0时,JVM唤醒班长线程,执行锁门。
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t班长关门"); // main线程
}
/*
没加CDL之前: 加了之后:
0 离开了教室 0 离开了教室
3 离开了教室 3 离开了教室
4 离开了教室 4 离开了教室
main 班长关门 2 离开了教室
2 离开了教室 1 离开了教室
1 离开了教室 main 班长关门
*/
}
2、同步屏障CyclicBarrier
(1)作用:循环屏障,它以 加法的形式 可以让一组线程到达一个屏障(同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行。简单的理解就是:人到齐了再开会。
(2)方法:
-
构造方法:
一般用第一个构造方法,parties代表需要设置的屏障数(阻塞线程的数量),Runnable barrierAction可以是屏障揭开后所执行的线程。 -
成员方法:
await()
(常用):调用该方法后,通知CyclicBarrier,本线程已经到达屏障,并进入等待阶段。只有当parties设定的所有线程数都调用过await()方法后,屏障才能打开,不然一直处于阻塞状态。
(3)使用:
【demo2】:集齐七颗龙珠,召唤神龙。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
// 设置需要阻塞的线程数为7,当阻塞的线程数达到7时,执行召唤神龙的线程。
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,() -> { System.out.println("召唤神龙"); });
// 创建7个线程执行,分别执行await()方法。
for(int i=1;i<=7;i++){
final int tempInt = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t收集到第"+tempInt+"颗龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
// 如果“收集龙珠”在await()方法之后执行,则“召唤神龙”会先执行。
// System.out.println(Thread.currentThread().getName()+"\t收集到第"+tempInt+"颗龙珠");
},String.valueOf(i)).start();
}
}
/*
输出:
1 收集到第1颗龙珠
5 收集到第5颗龙珠
4 收集到第4颗龙珠
3 收集到第3颗龙珠
2 收集到第2颗龙珠
6 收集到第6颗龙珠
7 收集到第7颗龙珠
召唤神龙
*/
}
注意:这里创建的7个线程中,执行任务的代码片段在cyclicBarrier.await();
之前才能符合同步屏障的效果,如果在cyclicBarrier.await();
之后执行收集龙珠
,则barrierAction中的召唤神龙
会先执行。所以执行代码的循序需要注意。
3、控制并发线程数的Semaphore
(1)作用:Semaphore(信号量)是用来控制 同时访问特定资源的线程数量,它通过协调各个线程,以保证合理的使用公共资源。
- Semaphore可以用于做流量控制,特别是公用资源有限的应用场景,比如数据库连接。只允许固定线程同时访问数据库连接,如果超出数据库连接数时,就会报无法获取数据连接。
(2)方法:
-
构造方法:
我们查看Semaphore的源码发现,它有两个构造方法。
其中permits表示能够允许同时并发的最大线程数。 -
成员方法:
acquire()
:从Semaphore中请求一个允许的资源。当只有一个资源可用时会阻塞。release()
:将一个资源释放回Semaphore。
(3)使用:
【demo3】:通过Semaphore来控制连接数据库的最大并发线程数。
public class SemaphoreTest {
private static final int THREAD_COUNT = 30;
// 利用线程池创建30个可执行的线程
private static ExecutorServicethreadPool = Executors.newFixedThreadPool(THREAD_COUNT);
// 通过Semaphore控制最大的线程并发数为10
private static Semaphore s = new Semaphore(10);
public static void main(String[] args) {
for (inti = 0; i< THREAD_COUNT; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
try {
// 每个线程访问的时候,都需要先acquire获取资格,使用完后需要release归还资格。
s.acquire();
System.out.println("save data");
s.release();
} catch (InterruptedException e) {
}
}
});
}
// 线程池使用完毕需要及时关闭
threadPool.shutdown();
}
}