并发工具的使用
前言:
之前的文章中学习了J.U.C中aqs的底层实现原理,这篇文学习一下J.U.C中提供的一些线程同步工具类。
Condition
在前面学习 synchronized 的时候,有讲到 wait/notify 的基本使用,结合 synchronized 可以实现对线程的通信。既然 J.U.C 里面提供了锁的实现机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢?
Condition 是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件(condition),只有满足条件时,线程才会被唤醒。
Condition 的基本使用
ConditionWait
public class ConditionDemoWait implements
Runnable {
private Lock lock;
private Condition condition;
public ConditionDemoWait(Lock lock,
Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("begin - ConditionDemoWait");
try {
lock.lock();
condition.await();
System.out.println("end - ConditionDemoWait");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}ConditionSignal
public class ConditionDemoSignal implements
Runnable {
private Lock lock;
private Condition condition;
public ConditionDemoSignal(Lock lock,
Condition condition) {
this.lock = lock;
this.condition = condition;
}
@Override
public void run() {
System.out.println("begin - ConditionDemoSignal");
try {
lock.lock();
condition.signal();
System.out.println("end - ConditionDemoSignal");
} finally {
lock.unlock();
}
}
}
通过这个案例简单实现了 wait 和 notify 的功能,当调用await 方法后,当前线程会释放锁并等待,而其他线程调用condition 对象的 signal 或者 signalall 方法通知并被阻塞的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。所以,condition 中两个最重要的方法,一个是 await,一个是 signal 方法
await:把当前线程阻塞挂起
signal:唤醒阻塞的线程
CountDownLatch
countdownlatch 是一个同步工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完毕再执行。从命名可以解读到 countdown 是倒数的意思,类似于我们倒计时的概念。
countdownlatch 提供了两个方法,一个是 countDown,一个是 await,countdownlatch 初始化的时候需要传入一个整数,在这个整数倒数到 0 之前,调用了 await 方法的程序都必须要等待,然后通过 countDown 来倒数。
使用案例
public static void main(String[] args) throws
InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(() -> {
System.out.println("" + Thread.currentThread().g
etName() + "-执行中");
countDownLatch.countDown();
System.out.println("" + Thread.currentThread().g
etName() + "-执行完毕");
}, "t1").start();
new Thread(() -> {
System.out.println("" + Thread.currentThread().g
etName() + "-执行中");
countDownLatch.countDown();
System.out.println("" + Thread.currentThread().g
etName() + "-执行完毕");
}, "t2").start();
new Thread(() -> {
System.out.println("" + Thread.currentThread().g
etName() + "-执行中");
countDownLatch.countDown();
System.out.println("" + Thread.currentThread().g
etName() + "-执行完毕");
}, "t3").start();
countDownLatch.await();
System.out.println("所有线程执行完毕");
}
从代码的实现来看,有点类似 join 的功能,但是比 join 更加灵活。CountDownLatch 构造函数会接收一个 int 类型的参数作为计数器的初始值,当调用 CountDownLatch 的countDown 方法时,这个计数器就会减一。通过 await 方法去阻塞去阻塞主流程。
Semaphore
semaphore 也就是我们常说的信号灯,semaphore 可以控制同时访问的线程个数,通过 acquire 获取一个许可,如果没有就等待,通过 release 释放一个许可。有点类似限流的作用。叫信号灯的原因也和他的用处有关,比如某商场就 5 个停车位,每个停车位只能停一辆车,如果这个时候来了 10 辆车,必须要等前面有空的车位才能进入。
使用案例
public class Test {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(5);
for (int i = 0; i < 10; i++) {
new Car(i, semaphore).start();
}
}
static class Car extends Thread {
private int num;
private Semaphore semaphore;
public Car(int num, Semaphore
semaphore) {
this.num = num;
this.semaphore = semaphore;
}
public void run() {
try {
semaphore.acquire();//获取一个许可
System.out.println("第" + num + "占用一个停车位");
TimeUnit.SECONDS.sleep(2);
System.out.println("第" + num + "俩车走喽");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
使用场景
Semaphore 比较常见的就是用来做限流操作了。
CyclicBarrier
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续工作。CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 当前线程已经到达了屏障,然后当前线程被阻塞。
使用场景
当存在需要所有的子任务都完成时,才执行主任务,这个时候就可以选择使用 CyclicBarrier
使用案例
public class DataImportThread extends Thread {
private CyclicBarrier cyclicBarrier;
private String path;
public DataImportThread(CyclicBarrier cyclicBarrier, String path) {
this.cyclicBarrier = cyclicBarrier;
this.path = path;
}
@Override
public void run() {
System.out.println("开始导入:"+path+" 位置的数据");
try {
cyclicBarrier.await();//阻塞
} catch
(InterruptedException e) {
e.printStackTrace();
} catch
(BrokenBarrierException e) {
e.printStackTrace();
}
}
}
注意点
1)对于指定计数值 parties,若由于某种原因,没有足够的线程调用 CyclicBarrier 的 await,则所有调用 await 的线程都会被阻塞;
2)同样的 CyclicBarrier 也可以调用 await(timeout, unit),设置超时时间,在设定时间内,如果没有足够线程到达,则解除阻塞状态,继续工作;
3)通过 reset 重置计数,会使得进入 await 的线程出现BrokenBarrierException;
4)如果采用是 CyclicBarrier(int parties, Runnable barrierAction) 构造方法,执行 barrierAction 操作的是最后一个到达的线程
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- TCP:测试小工具TCPing
- 对象实例化内存布局与访问定位 Krains 2020-08-14
- 基于Docker Compose部署分布式MinIO集群
- Go语言 | 从并发模式看channel使用技巧
- 字节码文件 Krains 2020-08-04
- 为WordPress添加自定义设置上传头像功能
- 229. 求众数 II Krains 2020-08-04 16:03:03 数学
- 部署 Consul服务实现Docker容器跨主机通信
- 【测试】 Java如何优雅的生成测试数据
- Spring事物的传播行为案例分析
- Ingress-nginx灰度发布功能详解
- [885]Tensorflow设置CUDA_VISIBLE_DEVICES来控制GPU的使用
- Windows10:启用或禁用休眠、保留的存储
- 基于Vue SEO的四种方案
- 一道SQL问题,你来试试的?