并发编程—并发工具类
这里将会记录一些并发工具类:CountDownLatch、CyclicBarrier、Semaphore
CountDownLatch
CountDownLatch是什么
CountDownLatch是在java1.5被引入的,存在于java.util.concurrent包下。它的作用是当一个线程任务完成后,它必须等待其它的线程的任务执行完成后,主线程才能继续往下执行。
CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1(这个可以根据业务去确定,也就是不一定就是减1)。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务。
CountDownLatch如何使用
方法说明:
- new CountDownLatch(6):我们实例化CountDownLatch时,需要传入一个参数[int]count,这个count就是计数器的数值。
- countDown():做减一的操作,每调用一次,计数器的数值就会减1。
- await():使线程阻塞,等待计数器的数值变成0时,放行阻塞的线程。
具体使用:
/**
* 定义
*/
private static CountDownLatch downLatch = new CountDownLatch(6);
/**
* 扣减的线程
*/
static class DownLatchThread implements Runnable {
@Override
public void run() {
// 业务代码
// countDown 可以理解为扣减1也就是 -1
downLatch.countDown();
System.out.println("Thread: " + Thread.currentThread().getId());
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 6; i++) {
// 启动六个线程 也就是说 countDown运行了六次 【-6】
new Thread(new DownLatchThread()).start();
}
// await(), 等待6个线程全部执行完
downLatch.await();
// 打印 可以看出这句话是六个线程运行完成之后才运行的
System.out.println("主线程运行");
}
运行的结果:
Thread: 11
Thread: 12
Thread: 13
Thread: 14
Thread: 15
Thread: 16
主线程运行
从上面的代码来看我们初始化了CountDownLatch的大小为6,然后启动6个线程,这六个线程都调用一次countDown()方法。从结果来看,主线程是等待6个线程运行完之后才执行的,这也体现了CountDownLatch的作用。【如果我启动五个线程,也就是调用五次countDown()方法,这时线程会一直阻塞。】
CyclicBarrier
CyclicBarrier是什么
从字面意思上来看CyclicBarrier就是循环(Cyclic)的屏障(Barrier);
作用:让一组线程达到某个屏障,被阻塞,一直到组内最后一个线程达到屏障时,屏障开放,所有被阻塞的线程会继续运行。
CyclicBarrier如何使用
方法说明
- new CyclicBarrier(6):实例化CyclicBarrier时传入参数count,count可以理解为线程的数量
- new CyclicBarrier(int count, Runnable runnable):当最后一个一组中的最后一个线程任务执行完后,指定一个任务执行
- await():调用的线程阻塞,直到一组线程中的最后一个线程调用await(),所有被awati()方法阻塞的线程都会被放行
- reset():将屏障重置为其初始状态。如果所有参与者目前都在屏障处等待,则它们将返回,同时抛出一个BrokenBarrierException。
具体使用:
/**
* 实例化CyclicBarrier
*/
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(6);
/**
* 工作线程
*/
static class BarrierThread implements Runnable {
@Override
public void run() {
try {
// 不是最后一个线程,阻塞
System.out.println("thread: "+ Thread.currentThread().getId() + " await.");
cyclicBarrier.await();
// 只有当最后一个线程的调用await之后,每个线程await()下面的代码才会执行
System.out.println("do something .");
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 6; i++) {
new Thread(new BarrierThread()).start();
}
System.out.println("主线程运行");
}
运行结果
主线程运行
thread: 13 await.
thread: 11 await.
thread: 12 await.
thread: 14 await.
thread: 15 await.
thread: 16 await.
do something .
do something .
do something .
do something .
do something .
do something .
这段代码也很简单,实例化一个大小为6的CyclicBarrier,启动6个线程每个线程都调用了await()方法。从打印的结果来看,当线程16运行之前,线程11,12,13,14,15都是阻塞状态,线程16 执行了await()方法后。所有的线程都被放行,继续执行await()方法后的代码。
CyclicBarrier与CountDownLatch比较
- CountDownLatch:一个线程(或者多个),等待另外N个线程完成某个事情之后才能执行;CyclicBarrier:N个线程相互等待,任何一个线程完成之前,所有的线程都必须等待。
- CountDownLatch:一次性的;CyclicBarrier:可以重复使用。
- CountDownLatch基于AQS;CyclicBarrier基于锁和Condition。本质上都是依赖于volatile和CAS实现的。
Semaphore
Semaphore是什么
字面意思:信号量
作用:控制同时访问某个特定资源的线程数量,用在流量控制
Semaphore如何使用
重要方法说明
- new Semaphore(int permits):初始化时,传入许可证的数量;
- acquire():获取许可证, -1;
- release():归还许可证, +1;
- hasQueuedThreads():是否有阻塞的线程;
- getQueueLength():得到阻塞线程的数量;
- availablePermits():可用许可证的数量;
具体使用
/**
* 座位数量10
*/
private final Semaphore seatCount = new Semaphore(10);
/**
* 构造方式私有化
*/
private UseSemaphoreDemo() {
}
private static UseSemaphoreDemo instance = null;
private static UseSemaphoreDemo getInstance() {
if (instance == null) {
synchronized (UseSemaphoreDemo.class) {
instance = new UseSemaphoreDemo();
}
}
return instance;
}
static class WorkThread extends Thread {
UseSemaphoreDemo semaphoreDemo = UseSemaphoreDemo.getInstance();
@Override
public void run() {
try {
// 获取座位使用权 就是调用 acquire() 方法
semaphoreDemo.seatCount.acquire();
System.out.println("线程Thread: " + Thread.currentThread().getId() +
"获取到使用权");
if (semaphoreDemo.seatCount.hasQueuedThreads()) {
// 是否有等待的线程
System.out.println("等待的线程数量:" + semaphoreDemo.seatCount.getQueueLength());
}
// 获取使用权后休眠
Thread.sleep(500 + Thread.currentThread().getId());
// 归还座位的使用权 调用release()方法
semaphoreDemo.seatCount.release();
System.out.println("线程Thread: " + Thread.currentThread().getId() +
"归还了使用权");
System.out.println("可用座位:" + semaphoreDemo.seatCount.availablePermits());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 60个线程去获取座位
for (int i = 0; i < 60; i++) {
new WorkThread().start();
}
}
上面代码的运行结果
线程Thread: 11获取到使用权
线程Thread: 12获取到使用权
线程Thread: 13获取到使用权
线程Thread: 14获取到使用权
线程Thread: 15获取到使用权
线程Thread: 16获取到使用权
线程Thread: 17获取到使用权
线程Thread: 18获取到使用权
线程Thread: 19获取到使用权
线程Thread: 20获取到使用权
线程Thread: 11归还了使用权
线程Thread: 21获取到使用权
可用座位:0
等待的线程数量:49
线程Thread: 12归还了使用权
线程Thread: 22获取到使用权
可用座位:0
等待的线程数量:48
线程Thread: 13归还了使用权
线程Thread: 23获取到使用权
...
上面代码的意思:一共10个座位,60个线程去获取使用权。
从控制台打印的结果来看:前10个线程迅速的拿到了座位的使用权,而之后的线程必须要等到拿到使用权的线程归还使用权后才能得到。这个也说明了Semaphore的作用。