多线程工具类CountDownLatch,CyclicBarrier,Semaphore
CountDownLatch和CyclicBarrier功能很像,
先说他们的区别吧,在我看来就两点,
1、CountDownLatch类似于计数器的减法,CyclicBarrier类似于计数器的加法
2、CountDownLatch中的计数器从构造注入的初始值减到0时,不重置,CyclicBarrier的计数器也有以一个初始值,不过CyclicBarrier是加法,当加到那个初始值的时候,重置为0。
一、CountDownLatch
* java.util.concurrent包下的多线程工具类
* CountDownLatch 类似于一个计数器,是一个同步工具类,协调多个线程之间的同步
* 特点: 做减法计算,当计数为0的时,唤醒阻塞线程
* 使用场景:执行主线程之前需要执行一些特定的线程
* CountDownLatch的缺点:CountDownLatch中的计数器从构造注入的初始值减到0时,不重置,不想CyclicBarrier可以重复使用。
举例:执行主线程之前,需要先执行50个子线程
public class MyCountDownLatch {
public static final int COUNTDOWNLATCHCOUNT = 50;
public static int i = 0;
static Lock lock = new ReentrantLock();
public static void inadd(){
lock.lock();
try{
i++;
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
try {
ExecutorService executorService = Executors.newFixedThreadPool(20);
CountDownLatch countDownLatch = new CountDownLatch(COUNTDOWNLATCHCOUNT);
for (int i = 0; i < COUNTDOWNLATCHCOUNT; i++) {
//初始化计数器的值
executorService.execute(new MyCountDownLatchThread(countDownLatch,i));
}
countDownLatch.await();
executorService.shutdown();
long endTime = System.currentTimeMillis();
System.out.println("---执行主线程---,执行其他线程耗时"+(double)(endTime-startTime)/1000+"秒");
System.out.println("--执行线程数:"+i);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static class MyCountDownLatchThread implements Runnable{
private CountDownLatch latch;
private int flag;
public MyCountDownLatchThread(CountDownLatch latch, int flag) {
super();
this.latch = latch;
this.flag = flag;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
try {
Random random = new Random();
int randomNum = random.nextInt((3000 - 1000)+1)+1000;//制造1000到3000之间的随机数
Thread.sleep(randomNum);
System.out.println("线程"+flag+"已执行完毕,耗时"+(double)randomNum/1000+"秒");
inadd();
latch.countDown();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
执行效果:
二、CyclicBarrier
* java.util.concurrent包下的多线程工具类
* CyclicBarrier大体的意思是可循环使用的屏障
* 特点:加计数方式,计数达到指定值时释放所有的等待线程,并计数重置为0重新开始
* 含义:用构造方法设置阻塞屏障数为n,调用await()方法计数加1,若加1后的值不等于构造器方法
* 设置的n值,则线程阻塞,达到n值时。释放所有的等待线程,计数值重新为0,以此类推。
* 优点:可重复使用
例子:人员面试,有三个环节(初面,二面,三面),总共有4个人员来面试,要求公司规定初面结束才能二面,三面,实现:
public class MyCyclicBarrier {
public static final int MS_COUNT = 3; //面试环节数量
//关键点:阻塞数等于面试人员数量,以每个人员为一个线程,每次面试,只有所有人都面过的时候,
//阻塞放开,执行下次面试。
public static final int PERSIONCOUNT = 4; //面试人员的数量
public static final int ZS_COUNT = 4; //阻塞数
public static void main(String[] args) {
try {
ExecutorService executorService = Executors.newFixedThreadPool(10);
CyclicBarrier cyclicBarrier = new CyclicBarrier(ZS_COUNT);
for (int i = 0; i < PERSIONCOUNT; i++) {
executorService.execute(new MyCyclicBarrierThread(cyclicBarrier,i));
}
executorService.shutdown();
}catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private static class MyCyclicBarrierThread implements Runnable{
private CyclicBarrier cer;
private int persionName;
public MyCyclicBarrierThread(CyclicBarrier cer, int persionName) {
super();
this.cer = cer;
this.persionName = persionName;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
for (int i = 0; i < MS_COUNT; i++) {
Random random = new Random();
int randomNum = random.nextInt((3000 - 1000)+1)+1000;//制造1000到3000之间的随机数
try {
Thread.sleep(randomNum);
switch (i) {
case 0:
System.out.println("人员"+persionName+"已通过初面,耗时"+(double)randomNum/1000+"天");
break;
case 1:
System.out.println("人员"+persionName+"已通过二面,耗时"+(double)randomNum/1000+"天");
break;
case 2:
System.out.println("人员"+persionName+"已通过三面,耗时"+(double)randomNum/1000+"天");
break;
default:
break;
}
this.cer.await();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
}
实现效果:
这里有个要强调的地方:
阻塞数等于面试人员数量,目的是以每个人员为一个线程,每次面试,只有所有人都面过的时候,
阻塞放开,执行下次面试。
否则:当阻塞数小于面试人员数量时,效果:
实现结果会出现问题。
当阻塞数大于面试人员数量时,效果:
原因是在for循环中,await()添加计数没有达到CyclicBarrier的屏障时,线程不会放开,会一直阻塞
三、Semaphore
* Semaphore翻译成字面意思为信号量,Semaphore可以控同时访问的线程个数,通过acquire()获取
* 一个许可,如果没有就等待,而release()释放一个许可。
Semaphore类位于java.util.concurrent包下,它提供了2个构造器:
public Semaphore(int permits) { //参数permits表示许可数目,即同时可以允许多少线程进行访问
sync = new NonfairSync(permits);
}
public Semaphore(int permits, boolean fair) { //这个多了一个参数fair表示是否是公平的,即等待时间越久的越先获取许可
sync = (fair)? new FairSync(permits) : new NonfairSync(permits);
}
acquire()、release()方法:
public void acquire() throws InterruptedException { } //获取一个许可
public void acquire(int permits) throws InterruptedException { } //获取permits个许可
public void release() { } //释放一个许可
public void release(int permits) { } //释放permits个许可
注意:
acquire()用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许可。
release()用来释放许可。注意,在释放许可之前,必须先获获得许可。
立即得到执行结果的方法:
public boolean tryAcquire() { }; //尝试获取一个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取一个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
public boolean tryAcquire(int permits) { }; //尝试获取permits个许可,若获取成功,则立即返回true,若获取失败,则立即返回false
public boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException { }; //尝试获取permits个许可,若在指定的时间内获取成功,则立即返回true,否则则立即返回false
例子:图书馆有3本书,但有5名学生预约,一本书只能由一名学术使用,只有使用完了其他人才能使用。
public class MySemaphore {
public static final int BOOKCOUNT = 3;
public static final int STUDENTCOUNT = 5;
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
Semaphore semaphore = new Semaphore(BOOKCOUNT); //书本数目
for (int i = 0; i < STUDENTCOUNT; i++) {
executorService.execute(new MySemaphoreThread(semaphore,i));
}
executorService.shutdown();
}
private static class MySemaphoreThread implements Runnable{
private Semaphore semaphore;
private int num;
public MySemaphoreThread(Semaphore semaphore, int num) {
super();
this.semaphore = semaphore;
this.num = num;
}
@Override
public void run() {
// TODO Auto-generated method stub
synchronized (this) {
try {
this.semaphore.acquire();
System.out.println("学术"+num+"借出一个书本");
Random random = new Random();
int randomNum = random.nextInt((3000 - 1000)+1)+1000;//制造1000到3000之间的随机数
Thread.sleep(randomNum);
System.out.println("学术"+num+"还回一个书本");
this.semaphore.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
效果: