简介
在jdk并发包下提供了几个很有用的并发工具类。CountDownLatch、CycleBarrier、Semaphore、Exchanger。通过它们可以在不同场景下完成特定的一些功能。
CountDownLatch
简介
CountDownLatch一般会把它称之为闭锁,其允许一个或多个线程等待其它线程完成操作。
CountDownLatch内部是通过计数器实现的,当执行某个节点后,就会开始等待其它任务的执行,每完成一个任务计数器就减一,当计数器等于0时,代表全部任务已完成,则恢复之前等待的线程继续向下执行。
使用场景
根据其工作的特性,使用场景也是比较多的。假设现在要解析一个Excel文件,其内部会有多个sheet,则设定每个线程解析一个sheet,等解析完所有sheet后,在进行后续操作,这就是一个很常见的场景。
代码实现
public class CountDownLatchDemo {
static CountDownLatch countDownLatch = new CountDownLatch(5);
//任务线程
private static class TaskThread implements Runnable{
@Override
public void run() {
countDownLatch.countDown();
System.out.println("task thread is running");
}
}
//等待线程
private static class WaitThread implements Runnable{
@Override
public void run() {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("wait thread is running");
}
}
public static void main(String[] args) throws InterruptedException {
//等待线程执行
for (int i = 0; i < 2; i++) {
new Thread(new WaitThread()).start();
}
for (int i = 0; i < 5; i++) {
new Thread(new TaskThread()).start();
}
TimeUnit.SECONDS.sleep(3);
}
}
CyciBarrier同步屏障
简介
Cycibarrier翻译过来叫做可循环的屏障。其可以实现当一组线程执行时,当到达某个屏障(同步点)被阻塞直到最后一个线程到达屏障后,才会让这一组线程继续向下执行,其内部也是基于计数器实现的。
对于Cycibarrier来说,其在基本流程的基础上,也就进行了一个扩展。查看源码可知,其构造函数不仅可以传入需要等待的线程数,同时可以传入一个Runnable。对于这个runnable可以作为一个扩展任务来使用。
代码实现
基础实现
public class CyclicBarrierDemo {
static CyclicBarrier barrier = new CyclicBarrier(3);
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}).start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}).start();
//主线程
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}
}
结果:
Thread-0: do somethings
main: do somethings
Thread-1: do somethings
main:continue somethings
Thread-1:continue somethings
Thread-0:continue somethings
根据运行结果可知,子线程与主线程首先会进行相互等待,只有等待其他线程执行完毕后,才能继续向下执行。因为主线程和子线程是由CPU来进行调度的,所以顺序不可控。
此时如果将线程数由3改成4则会永久等待,因为没有第四个线程执行await()方法,即没有第四个线程到达屏障,所以之前到达屏障的三个线程都不会继续向下执行。
扩展实现
CyclicBarrier还提供了一个更高级的构造函数,不仅可以设置等待线程数,同时还能够设置一个优先执行的Runnable,方便处理更为复杂的业务场景。
public class CyclicBarrierDemo2 {
static CyclicBarrier barrier = new CyclicBarrier(3,new ExtendTask());
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}).start();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}).start();
//主线程
try {
System.out.println(Thread.currentThread().getName()+": do somethings");
barrier.await();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":continue somethings");
}
static class ExtendTask implements Runnable{
@Override
public void run() {
System.out.println("extend task running");
}
}
}
与CountDownLatch区别
- CountDownLatch.await一般阻塞工作线程,所有的进行预备工作的线程执行countDown,而CycliBarrier通过工作线程调用await从而自行阻塞,直到所有工作线程到达指定屏障,再大家一起往下执行。
- 在控制多个线程同时运行上,CountDownLatch可以不限线程数量,而CycliBarrier时固定线程数。
Semaphore信号量
简介
其可以用于做流量控制,通过控制同时访问资源的线程数量,从而保证资源能够被更加合理的使用,如连接资源。假设现在有几万个文件资源,那么现在就可以开启若干个线程进行并发读取。但是读取后还要把这些数据写入到数据库。而数据库连接现在只有100个,此时就需要人为干预,控制只有100个线程同时获取数据库连接资源保存数据。
代码实现
public class SemaphoreDemo {
private static final int THREAD_COUNT=30;
private static ExecutorService executorService = Executors.newFixedThreadPool(THREAD_COUNT);
static Semaphore semaphore = new Semaphore(10);
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
executorService.execute(()->{
try {
//获取资源
semaphore.acquire();
System.out.println("do somethings");
//释放资源
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
executorService.shutdown();
}
}
根据上述实现,虽然有三十个线程执行,但是每次同时只能有十个线程能够获取资源。如果将释放资源API注释,则只有10条打印,因为资源耗尽,其他线程无法获取资源。
Exchange交换机
简介
Exchange是一个线程协作的工具类,可以进行线程间的数据交换,但是只局限于两个线程间协作,它提供了一个同步点,在这个同步点,两个线程可以交换彼此的数据。
代码实现
public class ExchangerDemo {
private static final Exchanger<Set<String>> exchange = new Exchanger<Set<String>>();
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
Set<String> setA = new HashSet<String>();//存放数据的容器
try {
setA.add("a1");
setA = exchange.exchange(setA);//交换set
/*处理交换后的数据*/
System.out.println(Thread.currentThread().getName()+" : "+setA.toString());
} catch (InterruptedException e) {
}
}
},"setA").start();
new Thread(new Runnable() {
@Override
public void run() {
Set<String> setB = new HashSet<String>();//存放数据的容器
try {
/*添加数据
* set.add(.....)
* set.add(.....)
* */
setB.add("a2");
setB = exchange.exchange(setB);//交换set
/*处理交换后的数据*/
System.out.println(Thread.currentThread().getName()+" : "+setB.toString());
} catch (InterruptedException e) {
}
}
},"setB").start();
}
}
运行结果
setB : [a1]
setA : [a2]