概述
并发协同就是多个线程共同完成一件事情,主要的实现方式就是根据实际情况堵塞某些线程,等待某些线程完成一定的工作之后再执行。
协同主要方法
- 基础传统的Synchronized及Object的wait,notify,notifyAll等方法
- 基于lock和Condition的wait
- 使用并发的工具类,可以很方便灵活的实现线程的并发协同
协同要思考的问题
- 什么地方需要协同
- 什么线程在什么时候需要等待
常用API(java并发包中的)
CountDownLatch
CountDownLatch是一个倒数的计数器,初始化时设置一个值,子线程完成后,计数器归零时则不再阻塞。这样可以保证在一定数量的线程执行完毕后再执行后续的操作。
主要用法
- 构造函数CountDownLatch(int count),指定等待线程的数量
- 等待方法await();当计数大于0时会阻塞线程,计数为0时不会阻塞
- 条件完成减数方法countDown();
- boolen await(long timeout,TimeUnit unit)设置等待的最大时长,返回true表示count清0了,false表示count不为0,但时间到了
- long getCount(),获取当前的计数
使用示例
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch=new CountDownLatch(5);
String[] names=new String[]{"1号","2号","3号","4号","5号"};
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达");
latch.countDown();
}
},names[i]).start();
}
latch.await();
System.out.println("所有人都到齐了");
}
输出结果:
我是5号已经到达
我是3号已经到达
我是1号已经到达
我是4号已经到达
我是2号已经到达
所有人都到齐了
CyclicBarrier 循环屏障
指定要协同的线程数量,并构成一个屏障,让线程在这个屏障前等待,当等待的数量达到设置值时,屏障放开。放开后又可重新使用,所以是循环屏障。与CountDownLatch都能设置固定数量的线程协同,但是CountDownLatch是做减法,减完之后就没了,CyclicBarrier是做加法,加到一定数量放开屏障并能循环使用。
主要方法
- 构造方法 CyclicBarrier(int parties) 指定屏障拦截的线程数
- 构造方法 CyclicBarrier(int parties,Runable advice) 指定屏障拦截的线程数和等待线程数达到设置值时触发的一个方法
- int await();线程调用此方法表示自己到达屏障前,若此时等待的线程数量不满足则此方法会造成当前线程阻塞,直到等待的线程数达到屏障的设置值
- int await(long timeout,TimeUnit unit);设置等待的最大时间,若超过时间还没释放就抛出异常
- int getNumberWaiting();获取等待在屏障前的线程数
- boolean isBroken();屏障是否被破坏
- void reset();重设屏障,如果屏障中存在等待的线程,则这些线程将会被释放,同时这个方法将抛出异常
使用示例
public static void main(String[] args){
CyclicBarrier barrier=new CyclicBarrier(5,()->{System.out.println("继续出发");});
String[] names=new String[]{"1号","2号","3号","4号","5号"};
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达");
try {
barrier.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (BrokenBarrierException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达");
try {
barrier.await();
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (BrokenBarrierException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达");
try {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
},names[i]).start();
}
}
输出结果:
我是2号已经到达
我是3号已经到达
我是4号已经到达
我是5号已经到达
我是1号已经到达
继续出发
我是1号已经到达
我是4号已经到达
我是3号已经到达
我是5号已经到达
我是2号已经到达
继续出发
我是4号已经到达
我是2号已经到达
我是3号已经到达
我是1号已经到达
我是5号已经到达
继续出发
Phaser 阶段协同器(以后补充)
Phaser是java7提供的一个协同工具类,包含了CountDownLatch和CyclicBarrier的特点。
主要方法
构造器
- Phaser();初始任务为0
- Phaser(int parties);指定初始任务
- Phaser(Phaser parent);指定父协同器,子协同器会作为一个整体注册到父协同器中,如果子协同器中没有任务则会自动从父协同器中解除注册
- Phaser(Phaser parent,int parties);指定父协同器,自己的任务数
增减任务
- int register();增加一个任务,返回当前的阶段号
- int bulkRegister(int parties);批量增加任务
- int arriveAndDeregister();减少一个任务并返回当前阶段号
到达、等待
- int arrive();到达(任务完成),返回当前阶段号
- int arriveAndAwaitAdvance();到达后等待其他任务到达(和屏障的功能类似)
- int awaitAdvance(int phase);在指定阶段等待(必须是当前阶段才有效)
- int awaitAdvanceInteruptibly(int phase);
- int awaitAdvanceInterruptibly(int phase,long timeout,TimeUnit unit);
阶段到达发出动作
- protected boolean onAdvance(int phase,int registeredParties);
其他API
- void forceTermination();强制结束
- int getPhase();返回当前阶段号
- boolean isTerminated();判断是否结束
使用示例(和CyclicBarrier类似)
public static void main(String[] args){
Phaser phaser=new Phaser(5);
String[] names=new String[]{"1号","2号","3号","4号","5号"};
for(int i=0;i<5;i++){
new Thread(new Runnable() {
@Override
public void run(){
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达第一阶段");
try {
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (BrokenBarrierException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达第二阶段");
try {
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e1) {
e1.printStackTrace();
} catch (BrokenBarrierException e1) {
e1.printStackTrace();
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}//阻塞3s
System.out.println("我是"+Thread.currentThread().getName()+"已经到达第三阶段");
try {
phaser.arriveAndAwaitAdvance();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}
},names[i]).start();
}
}
输出结果:
我是2号已经到达第一阶段
我是3号已经到达第一阶段
我是4号已经到达第一阶段
我是5号已经到达第一阶段
我是1号已经到达第一阶段
我是1号已经到达第二阶段
我是4号已经到达第二阶段
我是3号已经到达第二阶段
我是5号已经到达第二阶段
我是2号已经到达第二阶段
我是4号已经到达第三阶段
我是2号已经到达第三阶段
我是3号已经到达第三阶段
我是1号已经到达第三阶段
我是5号已经到达第三阶段
Semaphone计数信号量
Semaphone维护着一个许可的集合,有一定的初始许可数量,线程可以从中获取一个或多个许可,执行完操作后可以释放许可。Semaphone这样可以用来控制并发量,只有获取到许可的线程才能执行。
主要方法
- Semaphone(int parmits):permits初始许可数,非公平获取
- Semaphone(int parmits, boolean fair):fair=true公平获取,fair=false非公平获取
- acquire(int coount);获取许可
- tryAcquire();
- release();释放许可
使用示例
public static void main(String[] args){
Semaphone semaphone=new Semaphone(20);
Lock lock=new ReentrantLock();
for(int i=0;i<5;i++){
new Thread(()->{
lock.lock();
semaphore.acquire(1);
.....
semaphore.release(1);
lock.unlock();
}).start();
}
}
总结
- CountDownLatch、CyclicBarrier、Phaser都可等待后一起执行
- CyclicBarrier、Phaser可以有多个阶段等待后一起执行
- CyclicBarrier、Phaser都可以有多个阶段,但是CyclicBarrier的参与数是固定的,Phaser可以在中途添加和修改参与数
- Semaphone可以用来控制并发数量