CountDowLatch
-
用法
(1) CountDowLatch 用来同步一个或多个线程, 强制这些线程等待由其它线程执行的一组操作完成
(2) 可以向 CountDowLatch 设置一个初始计数, 任何在这个对象上调用wait()方法
的线程都会阻塞挂起,等待内部计数值为0
; 其它线程在完成工作时, 可以调用 CountDowLatch 的countDown()方法来减小计数值
(3) CountDowLatch的计数值不能被重置, 因此, 一旦一个CountDowLatch对象的计数值到达0, 就再也无法提供阻塞功能; 要使用可重置计数版本的CountDowLatch, 可以使用CyclicBarrier -
示例
Task类随机休眠一段时间, 模拟这部分工作的完成; WaitingTask表示系统中必须要等待的部分, 要等待问题的初始部分完成为止
CyclicBarrier
-
CyclicBarrier表示什么意思
CyclicBarrier适合这样的情况: 有一个任务A需要等待其它一组任务Group全都完成之后再执行; 当任务A执行完毕后, A再次等待任务组Group全部执行完再执行. 相当于这是一个一组任务和一个任务之间交替等待的过程 -
CyclicBarrier构造函数
构造器:public CyclicBarrier(int parties, Runnable barrierAction)
, 有两个参数
(1)parties
: CyclicBarrier计数器初始值(也是线程组的线程个数)
(2)barrierAction
: 当计数器值变为0时执行该线程(同时线程组所有线程执行完毕)CyclicBarrier只有一个方法:
await()
, 当线程组的一个线程执行完后, 要调用await()
方法让CyclicBarrier内部的计数器值-1, 当内部计数值变为0时, barrierAction线程就会执行, 当这个线程执行完后, 计数器重置为初始值, 且调用await()
的线程组中的线程会返回继续执行 -
使用CyclicBarrier进行仿真赛马游戏
游戏说明: 一个赛马比赛, 跑到长度为FINISH_LINE
, 有nHorses
匹马进行比赛. 每次让每匹马随机的跑n步, 循环进行, 直到有一匹马先跑完整个赛道获得冠军
(1) 马匹
class Horse implements Runnable {
static AtomicInteger count = new AtomicInteger();
static Random r = new Random(47);
private final int id = count.getAndIncrement();
private int strides; // 已经跑了多少步
private CyclicBarrier barrier;
public Horse(CyclicBarrier barrier) {
this.barrier = barrier;
this.strides = 0;
}
public int getStrides() {
return strides;
}
@Override
public void run() {
try {
// 一次循环向前跑几步, 然后等待所有马向前跑一次
while (!Thread.interrupted()) {
strides += r.nextInt(10);
barrier.await(); // 跑完一次, 进入等待
}
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return "Horse{" +
"id=" + id +
'}';
}
// 打印该匹马已经跑过的距离
public void printTrace() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < this.strides; i++) {
sb.append("*");
}
sb.append(this.id);
System.out.println(sb.toString());
}
}
(2) 比赛
public class HorseRace {
static final int RACE_LENGTH = 20; // 跑道长度
ArrayList<Horse> horses = new ArrayList<Horse>();
private CyclicBarrier barrier; // 用于同步的栅栏
private ExecutorService service;
public HorseRace(int horseCount) {
// 初始化线程池
this.service = Executors.newFixedThreadPool(horseCount);
// 初始化 CyclicBarrier
this.barrier = this.initBarrier(horseCount);
// 初始化马匹
for (int i = 0; i < horseCount; i++) {
horses.add(new Horse(this.barrier));
}
}
public void start() {
for (Horse horse : horses) {
service.execute(horse);
}
}
private CyclicBarrier initBarrier(int horseCount) {
return new CyclicBarrier(horseCount, new Runnable() { // CyclicBarrier 一次循环归0后执行的操作
@Override
public void run() {
System.out.println("[本轮跑步结束 ---------------------------------------------]");
// 一轮结束后, 打印马匹的轨迹
for (Horse horse : horses) {
horse.printTrace();
}
// 判断是否有马匹胜出
for (Horse horse : horses) {
if (horse.getStrides() >= RACE_LENGTH) {
System.out.println(horse + "胜出");
service.shutdownNow();
return;
}
}
// 每轮跑步有停顿时间
try {
Thread.sleep(2 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
public static void main(String[] args) {
new HorseRace(7).start();
}
}
Exchanger
- Exchanger用处
Exchanger是2个线程之间交换对象的栅栏; 它的典型应用场景是: 一个线程在创建对象.这些对象的床架代价高昂, 而另一个线程在消费对象. 通过这种方式, 可以有更多的对象在被创建的同时被消费 - 方法
当一个线程调用exchange()方法时将会阻塞挂起, 直到另一个线程也调用了exchange()方法时, 2个线程的exchange()可以运行完毕, 而持有的对象将会交换 - 仿真
如下创建了2个线程的单一Exchanger, Producer用于在CopyOnWriteList中塞入数据, Consumer使用Exchanger和Producer交换构造好的list消费其中的对象(使用remove元素的方式模拟消耗对象). CopyOnwriteList不会产生并发修改异常, 但会频繁触发gc