一、CountDownLatch(线程计数器 )
CountDownLatch 类在 java.util.concurrent 包下,具有计数器的功能,等待其他线程执行完毕,主线程在继续执行,用于监听某些初始化操作,并且线程进行阻塞,等初始化执行完毕后,通知主线程继续工作执行
final CountDownLatch latch = new CountDownLatch(2);
new Thread(){
public void run() {
System.out.println("子线程1"+Thread.currentThread().getName()+"正在运行");
Thread.sleep(3000);
System.out.println("子线程1"+Thread.currentThread().getName()+"执行成功");
latch.countDown();
};}.start();
new Thread(){
public void run() {
System.out.println("子线程2"+Thread.currentThread().getName()+"正在运行");
Thread.sleep(3000);
System.out.println("子线程2"+Thread.currentThread().getName()+"执行成功");
latch.countDown();
};}.start();
System.out.println("等待 2 个子线程的执行。。。");
latch.await();
System.out.println("2个子线程已经全部执行成功");
System.out.println("继续执行主线程");
}
二、CyclicBarrier(回环栅栏-等待至 barrier 状态再全部同时执行)
回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环
是因为当所有等待线程都被释放以后,CyclicBarrier 可以被重用。被重用的时候叫做barrier,当调用 await()方法之后,线程就处于 barrier 了
CyclicBarrier 中的 await 方法
//用来挂起当前线程,直至所有线程都到达 barrier 状态再同时执行后续任务
public int await();
//让这些线程等待至一定的时间,如果还有线程没有到达 barrier 状态就直接让到达 barrier 的线程执行后续任务。
public int await(long timeout, TimeUnit unit);
使用代码如下:
public static void main(String[] args) {
int N = 4;
CyclicBarrier barrier = new CyclicBarrier(N);
for(int i=0;i<N;i++) new WriterTest(barrier).start();
}
static class WriterTest extends Thread{
private CyclicBarrier clb;
public WriterTest(CyclicBarrier clb) {
this.clb = clb;
}
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"写入数据成功,正在等待其他线程的写入");
clb.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch(BrokenBarrierException e){
e.printStackTrace();
}
System.out.println("线程已全部写入,可以继续处理其他任务~~");
}
}
三、Semaphore(信号量-控制同时访问的线程个数)
Semaphore 翻译成字面意思为 信号量,Semaphore 可以控制同时访问的线程个数,通过
acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可
Semaphore 类中比较重要的几个方法:
//用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
public void acquire();
//获取 permits 个许可
public void acquire(int permits);
//释放许可。注意,在释放许可之前,必须先获获得许可。
public void release() {}
//释放 permits 个许可
public void release(int permits) {};
/*这四个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法*/
//尝试获取一个许可,若获取成功,则立即返回 true,若获取失败,则立即返回 false
public boolean tryAcquire();
//尝试获取一个许可,若在指定的时间内获取成功,则立即返回 true,否则立即返回 false
public boolean tryAcquire(long timeout, TimeUnit unit);
//尝试获取 permits 个许可,若获取成功,则立即返回 true,若获取失败,则立即返回 false
public boolean tryAcquire(int permits);
// 尝试获取 permits个许可,若在指定的时间内获取成功,则立即返回 true,否则则立即返回 false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit);
//还可以通过 availablePermits()方法得到可用的许可数目。
若一个工厂有 5 台机器,但是有 8 个工人,一台机器同时只能被一个工人使用,只有使用完
了,其他工人才能继续使用。那么我们就可以通过 Semaphore 来实现
static class WorkerTest extends Thread{
private int num;
private Semaphore semaphore;
public WorkerTest(int num,Semaphore semaphore){
this.num = num;
this.semaphore = semaphore;
}
@Override
public void run() {
try {
semaphore.acquire();
System.out.println("工人"+this.num+"占用一个机器在生产...");
Thread.sleep(2000);
System.out.println("工人"+this.num+"释放出机器");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int N = 8; //工人数
Semaphore semaphore = new Semaphore(5); //机器数目
for(int i=0;i<N;i++) new WorkerTest(i,semaphore).start();
}
方法小结
CyclicBarrier和CountDownLatch的区别
countDownLatch | CyclicBarrier |
---|---|
减计数方式 | 加计数方式 |
计算为0时释放所有等待的线程 | 计数达到指定值时释放所有等待线程 |
计数为0时,无法重置 | 计数达到指定值时,计数置为0重新开始 |
调用countDown()方法计数减一,调用await()方法只进行阻塞,对计数没任何影响 | 调用await()方法计数加1,若加1后的值不等于构造方法的值,则线程阻塞 |
不可重复利用 | 可重复利用 |
- CountDownLanch是为计数器是设置一个值,当多次执行countdown后,计数器减为0的时候所有线程被唤醒,然后CountDownLanch失效,只能够使用一次
- CyclicBarrier是当count为0时同样唤醒全部线程,同时会重新设置count为parties,重新new一个generation来实现重复利用
- CountDownLatch:一个或者多个线程,等待其他多个线程完成某件事情之后才能执行
- CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行
CyclicBarrier 小结
CyclicBarrier采用一个内部类Generation来维护当前循环,每一个await方法都会存储当前的generation,获取到相同generation对象的属于同一组,每当count的次数耗尽就会重新new一个Generation并且重新设置count的值为parties,表示进入下一次新的循环。 从这个await方法我们是不是可以知道只要有一个线程被中断了,当代的 generation的broken 就会被设置为true,所以会导致其他的线程也会被抛出BrokenBarrierException。相当于一个失败其他也必须失败,感觉有“强一致性“的味道
Semaphore 工作机制
信号量就是就是用来保证两个或多个关键代码段(公共资源)不被并发调用,就像火车上的厕所一样,不允许多余1个人同时使用,厕所就相当于公共资源,当一个人进入厕所后,该公共资源(厕所)即被占用,除非那个人从厕所出来(释放公共资源),别的人(进程)才可以使用厕所(公共资源)
信号量是一个非负整数,所有通过它的进程、线程都会将该整数减1,当该整数值为0时,所有试图通过它的进程都将处于等待状态,在信号量上我们定义两种操作,wait(个人理解为申请比较好)和Release(释放),当一个进程调用wait操作时,它要么得到资源后将信号量减1,要么一直等下去(放入阻塞队列),直到信号量大于等于1时
简单的流程示意图