Java 1.8 多线程之“线程同步工具”
jdk中提供的线程同步工具主要有:
- CountDownLatch
- CyclicBarrier
- Phaser
1.CountDownLatch
CountDownLatch 倒计时数闩 属于线程同步工具,一个或者多个线程等待n个线程执行完后才能继续执行。例如一个发送邮件的系统有3个线程在执行发送任务,当系统要重启的时候必须等所有线程都发送完毕邮件后才能重启系统(发起关闭的时候已经停止接受新的任务)。
public static void main(String[] args) {
/**
* 使一个或者多个线程等待另一个或者多个线程执行完后再继续执行。
*
* CountDownLatch(3); 初始化计数器为3
* 在计数器不为0时所有调用过await()方法的线程都将等待。
* 每当调用一次countDown()时计数器减一
* 直到计数器为0时唤醒所有等待的线程
*/
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
try {
// 等待所有发送邮件的线程都执行完了之后,重启系统。
countDownLatch.await();
System.out.println("... restart ...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 1");
// 模拟发送剩余邮件。
Thread.sleep(3000);
// 通知关闭线程,第一个thread已经发送完毕。
countDownLatch.countDown();
System.out.println("thread 1 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 2");
// 模拟发送剩余邮件。
Thread.sleep(2000);
// 通知关闭线程,第二个thread已经发送完毕。
countDownLatch.countDown();
System.out.println("thread 2 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 3");
// 模拟发送剩余邮件。
Thread.sleep(5000);
// 通知关闭线程,第三个thread已经发送完毕。
countDownLatch.countDown();
System.out.println("thread 3 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
2.CyclicBarrier
CyclicBarrier 循环屏障 同样也是线程同步工具,区别于CountDownLatch CyclicBarrier 是当一组线程到达确定的点的时候调用了await(),当最后一个线程调用完await()后CyclicBarrier 会释放所有等待中的线程不用像CountDownLatch那样在减一,另外CountDownLatch 初始化后计数器的值不可改变CyclicBarrier在使用完成后计数器可以重置。CyclicBarrier还比CountDownLatch多了一个功能,在所有阻塞线程得以执行的时候CyclicBarrier可以回调一个方法。
例 开启3条线程分析三个日志文件,将结果统一拼接成一条sql语句,高效的插入数据库。
public class Test {
static CyclicBarrier cyclicBarrier = new CyclicBarrier(3, new callBack());
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
try {
System.out.println("thread 1 read log");
// 读取日志
Thread.sleep(3000);
// 读取完毕等待数据拼接
cyclicBarrier.await();
System.out.println("thread 1 end");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 2 read log");
// 读取日志
Thread.sleep(2000);
// 读取完毕等待数据拼接
cyclicBarrier.await();
System.out.println("thread 2 end");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 3 read log");
// 读取日志
Thread.sleep(5000);
// 读取完毕等待数据拼接
cyclicBarrier.await();
System.out.println("thread 3 end");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(7000);
System.out.println("reset");
cyclicBarrier.reset();
System.out.println("剩余计数:" + cyclicBarrier.getParties() +
" 等待线程:" + cyclicBarrier.getNumberWaiting());
}
static class callBack implements Runnable {
@Override
public void run() {
System.out.println("我是回调方法,我要把读取到的数据存到数据库啦");
/**
* 不可再回调方法中重置cyclicBarrier否则会抛出BrokenBarrierException
*/
// cyclicBarrier.reset();
}
}
}
3.Phaser
Phaser是jdk1.7中concurrent包中新增的线程同步工具,此类第一行注释写到
* A reusable synchronization barrier, similar in functionality to
* {@link java.util.concurrent.CyclicBarrier CyclicBarrier} and
* {@link java.util.concurrent.CountDownLatch CountDownLatch}
* but supporting more flexible usage.
Phaser是类似CyclicBarrier和CountDownLatch的工具,但是更注重灵活性,Phaser非常强大可以动态注册或者取消注册来调整计数器的值。Phaser注重分段执行 当有分步骤执行当第一部分执行完后同步继续执行第二步的时候Phaser非常有用。
public static void main(String[] args) throws InterruptedException {
/**
* 初始化3个计数器,并且覆盖回调方法。
*/
Phaser phaser = new Phaser(3) {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("走完了一步啦!");
return super.onAdvance(phase, registeredParties);
}
};
new Thread(() -> {
/**
* 程序分三步执行,第一步等待三个线程同步继续执行
* 第二步动态移除了一个线程,两个线程到达标记点则继续执行
* 第三步一条线程不等待其他线程同步,只是在到达标记点的时候通知其他人我到了。
* 其他人收到后如果在等待中那么会继续执行。
*/
try {
System.out.println("thread 1 step 1");
Thread.sleep(3000);
// 到达标记点,并且等待同步
phaser.arriveAndAwaitAdvance();
System.out.println("thread 1 step 2");
Thread.sleep(1000);
// 到达标记点,并且等待同步
phaser.arriveAndAwaitAdvance();
System.out.println("thread 1 step 3");
Thread.sleep(4000);
// 到达标记点,无需同步继续执行
phaser.arrive();
System.out.println("thread 1 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(() -> {
try {
System.out.println("thread 2 step 1");
Thread.sleep(2000);
phaser.arriveAndAwaitAdvance();
System.out.println("thread 2 step 2");
Thread.sleep(3000);
phaser.arriveAndAwaitAdvance();
System.out.println("thread 2 step 3");
// 等待再次其他线程到达标记点。第三步演示只要全部到达标记点那么就可以继续执行。
phaser.arriveAndAwaitAdvance();
System.out.println("thread 2 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
/**
* 此线程只跟随两步,然后退出跟随。计数器减一,其他两个线程如果都“到达”那么会继续执行不会等待这个线程。
*/
new Thread(() -> {
try {
System.out.println("thread 3 step 1");
Thread.sleep(5000);
phaser.arriveAndDeregister();
System.out.println("thread 3 end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(700000);
}
register() :动态注册一个任务到Phaser 中。
bulkRegister(int) :取消注册n个人任务
arrive():标记当前任务已经到达标记点,但是不等待其他任务。
arriveAndDeregister():标记当前任务已经到达标记点,但是不等待其他任务。并取消本身在Phaser 中的注册。
awaitAdvance(int n):等待n个任务到达标记点后继续执行。
arriveAndAwaitAdvance():等待所有任务到达标记点的时候,才继续执行下面任务。
onAdvance():覆盖onAdvance方法可以实现完成每一步的时候回调次方法。