countdownlatch
countdownlatch是我们常用的同步类。但是在使用过程中有一个问题,就是他需要确定同步的次数。当我们的次数有变化的时候,就会对修改带来一定的问题。新增或者减少一个同步任务,只要次数没有跟着改变就会出现运行的问题。
- 新增了任务,会发现同步时数据不准确。同步线程比预期快运行。
- 减少任务,会发现线程一直等待,无法运行下去。
针对这种场景,我们常见的一种解决方法是维护一个数组,数组里是任务,countdownlatch的数量限制从数组获取,创建的任务也从数组获取。这样通过约定相同的数据源。满足变更的需要。这样就有一个地方是需要注意的。就是每个task里的countdownlatch是需要额外设置的。刚开始的时候countdownlatch也无法确认有多少的任务。所以经常看到的写法的样例如下。
首选构造一个可以填充countDownLatch的Runnable子类。
class Task implements Runnable {
CountDownLatch countDownLatch;
public void setCountDownLatch(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
try {
System.out.println("over");
} finally {
countDownLatch.countDown();
}
}
}
然后就是创建操作的流程了
Task[] runnables = new Task[]{new Task()};
CountDownLatch countDownLatch =new CountDownLatch(runnables.length);
for (Task runnable : runnables) {
runnable.setCountDownLatch(countDownLatch);
executorService.submit(runnable);
}
countDownLatch.await();
这样就做到了只添加任务,不需要管理其他的部分了。但是这也有一个问题。对java开发不友好。当task处理的任务没有相似性的时候,似乎lambda会带来更多的方便。lambda的操作还是比较单一,无法与上面的情况互相配合,lambda的时候就必须确定countDownLatch的个数了,所以无法把lambda表达式写到task数组里去。那是不是有方法可以动态的确定任务个数呢。phaser可以做到。
phaser
phaser的写法和countDownLatch类似,不过他自己又了维护增加和减少的方法。在每个task里,需要自己做增加和减少的操作。
Phaser phaser = new Phaser();
phaser.register();
executorService.submit(() -> {
try {
Thread.sleep(3000);
System.out.println("hello");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
phaser.arrive();
}
});
phaser.register();
phaser.arriveAndAwaitAdvance();
上面的demo中。
phaser.register();调用了2次。这两次的含义是不一样的。
第一次的是为了submit提交,他对应的是线程里的phaser.arrive();
第二次的是为了主线程阻塞同步, 利用phaser.arriveAndAwaitAdvance();进行同步操作,所以先自己做个增加。
看到这里或许有个疑问,就是为什么第一次的register不放在线程池内执行。
phaser本身判定的是次数是否为0。如果线程池进行了排队。那么线程的内容就无法被执行。register久久不能触发。
这样就会导致主线程自己做了增减,完成了判定为0的情况。
countDownLatch的一些问题phaser也有。就是线程池的拒绝策略触发时,线程内容无法被执行,所以无法触发减数操作,他们都会一直等待下去。