程序、进程、线程
- 程序:是存储在磁盘或其他存储设备中含有数据和指令的静态代码文件。
- 进程:是程序的一次执行过程,是系统运行程序的基本单元,因此进程是动态的。系统运行一个程序就是程序从创建、运行到消亡的过程。在运行程序时会占用系统资源,如CPU、内存、文件输入输出设备等。
- 线程:线程是比程序更小的执行单元,一个进程在其运行过程中可以产生多个线程。
进程与线程的区别
- 进程各自独立使用一块内存空间和一组系统资源,而同一进程中的多个线程则共享一块内存空间和一组系统资源。
- 切换工作时,线程之间的切换会比进程之间的切换负担小,因此,线程也被称为轻量级的进程。
- 进程同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段
多线程与单线程的区别与联系
- 操作系统将单核CPU分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用CPU的机制。多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。即:"微观上串行,宏观上并行"的机制。
- 结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间
- 多线程的好处
- 发挥多核CPU的优势
- 防止单线程长时间阻塞系统无响应
- 便于任务切分并建模
多线程的创建方式
- 继承Thread类
- 实现Runnable接口 + Thread
- Thread + 匿名内部类
- Java8 Lamda表达式
- Thread + FuturnTask + Callable(可获取返回值)
- Timer + 匿名内部类
- 线程池 + 任务
详情链接:https://blog.csdn.net/silence_yb/article/details/123873858
Callable 与 Runnable的区别
- 抽象方法不同
Callable抽象方法为:call()方法
Runnable抽象方法为:run()方法 - 返回值不同
Callable的返回值为泛型T
Runnable没有返回值为 - 异常处理不同
Callable可以抛出异常
Runnable无法抛出异常 - 运用在多线程方面不同
Callable配合FuturnTask + Thread使用
Runnable配合 Thread使用
线程停止
- 方式一:使用打断方式停止线程
- 方式二:使用volatile保证多线程间可见性停止多线程
详情连接
线程池
线程池优点
- 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
- 提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系 统的稳定性,使用线程池可以进行统一的分配,调优和监控
jdk创建的线程及使用场景
newFixedThreadPool
- 创建一个指定工作线程数量的线程池。 每当提交一个任务就创建一个工作线程,如果工作线程数量达到线程池初始的最大数,则将提交的任务存入到池队列中
- 特点
核心线程数 == 最大线程数(不会有临时线程被创建),因此也无需超时时间
阻塞队列(LinkedBlockingQueue)是无界的,可以放任意数量的任务 - 适用场景
适用于任务量已知,相对耗时的任务 - 源码
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
newCachedThreadPool
-
创建一个可缓存的线程池
-
特点
核心线程数是 0,最大线程数是 Integer.MAX_VALUE,临时线程的空闲生存时间是 60s,全部都是临时线程(60s 后可以回收)
队列采用了 SynchronousQueue, 实现特点是,它没有容量,没有线程来取是放不进去的 -
适用场景
整个线程池表现为线程数会根据任务量不断增长,上限为Integer.MAX_VALUE,当任务执行完毕,空闲1分钟后释放线程。 适合任务数比较密集,但每个任务执行时间较短的情况 -
源码
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(
0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- SynchronousQueue 队列特性测试代码
public static void main(String[] args) {
SynchronousQueue<Integer> integers = new SynchronousQueue<>();
new Thread(() -> {
try {
MyTool.printTimeAndThread("putting {} " + 1);
integers.put(1);
MyTool.printTimeAndThread("{} putted..." + 1);
MyTool.printTimeAndThread("putting...{} " + 2);
integers.put(2);
MyTool.printTimeAndThread("{} putted..." + 2);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
MyTool.sleep(1);
new Thread(() -> {
try {
MyTool.printTimeAndThread("taking {}" + 1);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
MyTool.sleep(1);
new Thread(() -> {
try {
MyTool.printTimeAndThread("taking {}" + 2);
integers.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t3").start();
}
newSingleThreadExecutor
- 创建一个单线程化的Executor,多个任务排队执行。线程数固定为 1,任务数多于 1 时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放,即只创建唯一的工作者线程来执行任务,如果这个线程异常结束,会有另一个取代它,保证顺序执行
- 特点
1.可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的
2.Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改:inalizableDelegatedExecutorService 应用的是装饰器模式,只对外暴露了 ExecutorService 接口,因此不能调用 ThreadPoolExecutor 中特有的方法 - 适用场景
多任务串行执行 - 源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
( new ThreadPoolExecutor(
1,
1,
0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 区别
1.与自己创建一个线程执行任务时的区别
自己创建一个单线程串行执行任务,如果任务执行失败而终止那么没有任何补救措施,而线程池还会新建一个线程,保证池的正常工作
2.与使用Executors.newFixedThreadPool(1) 初始时为1时区别
newFixedThreadPool(1) 方式创建的线程池对外暴露的是 ThreadPoolExecutor 对象,可以强转后调用setCorePoolSize 等方法进行修改,而newSingleThreadExecutor则使用装饰这模式,对外暴露了 ExecutorService 接口,不能修改ThreadPoolExecutor里的方法
newScheduleThreadPool
创建一个定长的线程池,而且支持定时的以及周期性的任务执行,类似于Timer
Executor、ExecutorService、Executors
Executor 和 ExecutorService区别
- ExecutorService 接口继承了 Executor 接口,是 Executor 的子接口
- Executor 接口定义了 execute()方法用来接收一个 Runnable接口的对象,而 ExecutorService 接口中的 submit()方法可以接受Runnable和Callable 接口的对象
- Executor 中的 execute() 方法不返回任何结果, 而 ExecutorService 中的 submit()方法可以通过一个 Future 对象返回运算结果
- ExecutorService 除了允许客户端提交一个任务,ExecutorService 还提供用来控制线程池的方法。比如:调用 shutDown() 方法终止线程池
Executors 类提供工厂方法用来创建不同类型的线程池
submit() 和 execute()方法的区别
- execute()方法的返回类型是void,submit()方法可以返回持有计算结果的Future对象
- execute()方法定义在Executor接口中,submit()方法定义在ExecutorService接口中
线程池大小的设计
- (1)高并发、任务执行时间短的业务,线程池线程数可以设置为CPU核数+1,减少线程上下文的切换
- (2)并发不高、任务执行时间长的业务要区分开看
1.假如是业务时间长集中在IO操作上,也就是IO密集型的任务,因为IO操作并不占用CPU,所以不要让所有的CPU闲下来,可以加大线程池中的线程数目,让CPU处理更多的业务
2.假如是业务时间长集中在计算操作上,也就是计算密集型任务,这个就没办法了,和(1)一样吧,线程池中的线程数设置得少一些,减少线程上下文的切换 - 并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于整体架构的设计,看看这些业务里面某些数据是否能做缓存是第一步,增加服务器是第二步,至于线程池的设置,设置参考(2)。 最后,业务执行时间长的问题,也可能需要分析一下,看看能不能使用中间件对任务进行拆分和解耦
多线程相关方法
Thread相关方法
方法名 | 功能说明 |
---|---|
start() | 启动一个新线程,在新的线程运行 run 方法中的代码 |
run() | 新线程启动后会调用的方法 |
join() | 等待线程运行结束方法 |
join(long n) | 带超时时间的等待线程运行结束方法 |
setPriority(int) | 设置线程优先级(int取值1-10) |
isInterrupted(int) | 判断是否被打断(不会清除打断标记) |
interrupt(int) | 打断线程(如果被打断线程正在 sleep,wait,join会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,则会设置 打断标记) |
interrupted() | 判断当前线程是否被打断(会清除打断标记) |
sleep(long n) | 让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程(该线程不会释放锁) |
yield() | 通知线程调度器让出当前线程对CPU的使用 |
Object相关方法
方法名 | 功能说明 |
---|---|
wait() | 让线程睡眠 |
notify() | 唤醒线程 |
notifyAll() | 唤醒所有线程 |
LockSupport相关方法
方法名 | 功能说明 |
---|---|
park() | 让线程睡眠 |
unPark(Thread t) | 唤醒t线程 |
Reentrant相关方法
方法名 | 功能说明 |
---|---|
await() | 让线程睡眠 |
signal() | 唤醒单个线程 |
signalAll() | 唤醒所有线程 |
详情连接:https://blog.csdn.net/silence_yb/article/details/124074945
线程状态
五种状态(操作系统分)
- 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
- 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联,可以由任务调用器执行
- 【运行状态】指获取了 CPU 时间片运行中的状态
- 当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,此时会导致线程的上下文切换
- 【阻塞状态】
- 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入【阻塞状态】
- 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
- 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑调度它们
- 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态
- 如图
六种状态(从JAVA Thread可分)
- NEW(初始状态):线程被初始化,但是还没有调用 start() 方法
- RUNNABLE(运行状态):当调用了 start() 方法之后,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的【就绪状态READY】【运行状态Running】
- BLOCKED (阻塞状态):表示线程阻塞于锁
- WAITING (等待状态):表示线程进入阻塞状态,进入状态后表示线程需要等待其他线程做出一些特定动作(通知或中断)
- TIMED_WAITING(超时等待状态):该状态不同于WAITING状态,它可以在指定的时间内自行返回
- TERMINATED(终止状态):表示当前线程已经执行完毕
- 如图
守护线程与用户线程
- 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程
- Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常
- 只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束
- 守护线程相当于后台管理者,比如 : 进行内存回收,垃圾清理等工作
public class DaemonThread {
public static void main(String[] args) {
MyTool.printTimeAndThread("main 开始运行...");
Thread t1 = new Thread(() -> {
while (true) {
MyTool.printTimeAndThread("t1 开始运行...");
MyTool.sleepMillis(2l);
//这句话不会打印,因为t1是守护线程,其他线程运行完该线程就会结束运行
MyTool.printTimeAndThread("运行结束...");
}
}, "daemon");
// 设置该线程为守护线程
t1.setDaemon(true);
t1.start();
MyTool.sleepMillis(1l);
MyTool.printTimeAndThread("main 运行结束...");
}
}
多线程问题
临界区 (Critical Section)
- 首先明确一点,一个程序运行多个线程本身没有问题
- 问题在于多个线程访问共享资源时,对共享资源进行读写操作时发生指令交错现象
- 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
竞态条件(Race Condition)
- 多个线程在临界区内执行,由于代码的执行顺序不同而导致结果无法预测,称之为发生了竞态条件
解决方案
-
阻塞式解决方案:
Synchronized 加锁
Lock 加锁
使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换 -
非阻塞式的解决方案
volatile 使用原子变量
synchronized 关键字详解:https://blog.csdn.net/silence_yb/article/details/124116212
volatile关键字详解:https://blog.csdn.net/silence_yb/article/details/124132058
AQS(AbstractQueuedSynchronizer)
概述
- AQS全称是 AbstractQueuedSynchronizer,AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。
- 一个表示资源状态的原子变量state(AQS没有初始值),子类可以通过操作这个原子变量就可以控制获取锁和释放锁。操作原子变量方法如下:
/**
* The synchronization state.
*/
private volatile int state;
// 获取状态
protected final int getState() {
return state;
}
//设置 state 状态
protected final void setState(int newState) {
state = newState;
}
/**
* cas 机制设置 state 状态
*/
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
-
一个FIFO的等待队列,类似于Monitor里的EntryList
-
两个资源状态(专享模式与共享模式)
- 独占模式是只有一个线程能够访问资源,如ReentrantLock
- 共享模式可以允许多个线程访问资源,如Semaphore/CountDownLatch
- 注:有些锁这两种资源状态都可以实现,如读写锁ReentrantReadWriteLock
-
多个条件变量,用来阻塞唤醒线程,类似于Monitor里的WaitSet
-
子类需要实现的抽象方法
isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。
tryAcquire(int):独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int):独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int):共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int):共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。
自定义一个锁类
- 自定义一个实现AQS的同步器
- 自定义一个锁类实现Lock接口并实现Lock里的抽象方法
- 私有化自定义实现了AQS的同步器
- 同步AQS里操作原子相关方法实现自定义锁类中的加锁、解锁过程
AQS框架工具类1—读写锁
读写锁的引入
- 如果在保证原子性的时候我们可以使用加锁的方式保证,但是当读操作远远高于写操作时,这时候使用读写锁让 读-读可以并发,提高性能。
- 基本使用方法
/**
* 读写锁使用场景一:对共享数据操作
* */
public class Demo01 {
// 共享数据
private Integer num = 0;
// 读写锁
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 读锁
private ReentrantReadWriteLock.ReadLock rl = rwl.readLock();
// 写锁
private ReentrantReadWriteLock.WriteLock wl = rwl.writeLock();
public Integer getData(){
rl.lock();
try {
MyTool.printTimeAndThread("获取数据...");
MyTool.sleep(2);
return num;
} finally {
rl.unlock();
}
}
public void setData(){
wl.lock();
try {
MyTool.printTimeAndThread("写入数据...");
num = num + 1;
} finally {
wl.unlock();
}
}
}
- 测试类
public class Test {
public static void main(String[] args) {
Demo01 demo01 = new Demo01();
new Thread(() -> {
MyTool.printTimeAndThread("读取数据");
Integer data = demo01.getData();
MyTool.printTimeAndThread("读取数据 " + data.toString());
},"t1").start();
MyTool.sleep(1);
new Thread(() -> {
MyTool.printTimeAndThread("读取数据");
Integer data = demo01.getData();
MyTool.printTimeAndThread("读取数据 " + data.toString());
},"t2").start();
//将下面的代码放开可以测试读写并发场景
// new Thread(() -> {
// MyTool.printTimeAndThread("写入数据");
// demo01.setData();
// MyTool.printTimeAndThread("写入数据完成");
// },"t3").start();
}
}
结论
- 读锁-读锁 可以并发
- 读锁-写锁 相互阻塞
- 写锁-写锁 相互阻塞
注意事项
- 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待
r.lock();
try {
// ...
w.lock();
try {
// ...
} finally{
w.unlock();
}
} finally{
r.unlock();
}
- 重入时降级支持:即持有写锁的情况下去获取读锁
class CachedData {
Object data;
// 是否有效,如果失效,需要重新计算 data
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
void processCachedData() {
rwl.readLock().lock();
if (!cacheValid) {
// 获取写锁前必须释放读锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try {
// 判断是否有其它线程已经获取了写锁、更新了缓存, 避免重复更新
if (!cacheValid) {
data = ...
cacheValid = true;
}
// 降级为读锁, 释放写锁, 这样能够让其它线程读取缓存
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock();
}
}
// 自己用完数据, 释放读锁
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
AQS框架工具类2—StampedLock
StampedLock的引入
- 为了进一步优化读取操作的性能,JDK8引入了该StampedLock类,该类获取读锁时会先返回一个戳,获取此戳的过程中不会进行cas操作, 当读取完成后,通过验戳可以知道在此过程中共享变量是否被修改过,没有修改过,则直接返回结果,若修改过,则需要进入锁升级过程,也就是将锁升级为读锁,然后再进行相关逻辑的处理
- 基本使用方法
public class MyStampedLock {
private final StampedLock lock = new StampedLock();
private int data = 1;
public int read(int readTime) {
long stamp = lock.tryOptimisticRead();
MyTool.printTimeAndThread("获取戳..." + stamp);
MyTool.sleep(readTime);
if (lock.validate(stamp)) { //校验获取到的戳是否有效
MyTool.printTimeAndThread("获取戳有效...读取结束" + stamp + "data:{}" + data);
return data;
}
// 锁升级 - 读锁
MyTool.printTimeAndThread("获取戳无效,锁升级..." + stamp);
try {
stamp = lock.readLock();
MyTool.printTimeAndThread("升级为读锁后,戳为:" + stamp);
MyTool.sleep(readTime);
MyTool.printTimeAndThread("升级为读锁后, 读取结束,戳为:" + stamp + "数据为:" + data);
return data;
} finally {
MyTool.printTimeAndThread("释放读锁,戳为: " + stamp);
lock.unlockRead(stamp);
}
}
public void write(int newData) {
long stamp = lock.writeLock();
MyTool.printTimeAndThread("获取写锁,戳为:" + stamp);
try {
MyTool.sleep(2);
this.data = newData;
} finally {
MyTool.printTimeAndThread("释放写锁,戳为:" + stamp);
lock.unlockWrite(stamp);
}
}
}
- 测试类
public static void main(String[] args) {
MyStampedLock stampedLock = new MyStampedLock();
//测试多读不升级为读锁场景
// testStampedLock01(stampedLock);
//测试一读一写,在读的过程中验戳失败,升级为读锁过程
testStampedLock02(stampedLock);
//测试多读一写,其中一个线程在读的过程中验戳失败,升级为读锁,另外一个线程在写的过程中获取戳并进行升级
//最后写锁是否,多读线程共同并发执行场景
testStampedLock03(stampedLock);
}
private static void testStampedLock01(MyStampedLock stampedLock) {
//两个线程同时去读,验证是否会升级为读锁
new Thread(() -> {
stampedLock.read(1);
},"t1").start();
new Thread(() -> {
stampedLock.read(1);
},"t2").start();
}
private static void testStampedLock02(MyStampedLock stampedLock) {
//同时开启线程去读和写,检验是否读的过程中会升级为读锁
new Thread(() -> {
stampedLock.read(1);
},"t3").start();
new Thread(() -> {
stampedLock.write(5);
},"t4").start();
}
private static void testStampedLock03(MyStampedLock stampedLock) {
//同时开启线程去读和写,检验是否读的过程中会升级为读锁
new Thread(() -> {
stampedLock.read(1);
},"t3").start();
new Thread(() -> {
stampedLock.write(5);
},"t4").start();
new Thread(() -> {
stampedLock.read(1);
},"t5").start();
}
StampedLock 注意点
- StampedLock 不支持条件变量
- StampedLock 不支持可重入
AQS框架工具类3—Semaphore
Semaphore的引入
- 如果想限制能同时访问共享资源的线程上限,可以使用Semaphore(信号量),该类可以替代obj.wait()/obj.notify()/obj.notifyAll工作方法
public class MySemaphore {
public static void main(String[] args) {
// 1. 创建 semaphore 对象,并设置访问共享资源线程的上线数
Semaphore semaphore = new Semaphore(3);
// 2. 10个线程同时运行
for (int i = 0; i < 10; i++) {
new Thread(() -> {
// 3. 获取许可
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
MyTool.printTimeAndThread("start...");
MyTool.sleep(1);
MyTool.printTimeAndThread("end...");
} finally {
// 4. 释放许可
semaphore.release();
}
}).start();
}
}
}
AQS框架工具类4—CountdownLatch
CountdownLatch的引入
用来进行线程同步协作,等待所有线程完成倒计时,其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一
package com.yanxb.aqs.jdkimpl;
import com.yanxb.util.MyTool;
import java.util.concurrent.CountDownLatch;
public class MyCountdownLatch {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
//测试3个线程中只有两个执行完毕,CountDownLatch中的state未改变成0,await()线程一直等待场景
// test01(latch);
//测试3个线程中全部执行完毕,CountDownLatch中的state改变成0,await()线程运行结束场景
test02(latch);
MyTool.printTimeAndThread("waiting...");
latch.await();
MyTool.printTimeAndThread("wait end...");
}
private static void test01(CountDownLatch latch) {
new Thread(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(1);
latch.countDown();
MyTool.printTimeAndThread("end..." + "此时CountDownLatch计数为:" + latch.getCount());
}).start();
new Thread(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(2);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + "此时CountDownLatch计数为:" + latch.getCount());
}).start();
}
private static void test02(CountDownLatch latch) {
new Thread(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(1);
latch.countDown();
MyTool.printTimeAndThread("end..." + "此时CountDownLatch计数为:" + latch.getCount());
}).start();
new Thread(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(2);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + "此时CountDownLatch计数为:" + latch.getCount());
}).start();
new Thread(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(1.5);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + "此时CountDownLatch计数为:" + latch.getCount());
}).start();
}
}
AQS框架工具类4—CyclicBarrier
CyclicBarrier的引入
- 当有一些场景需要重用CountDownLatch 对象时,但CountDownLatch 不能满足此场景,所以就引入了CyclicBarrier类
- CountDownLatch 对象是不可以被重用的,而CyclicBarrier对象可以被重用
- 使用方面:CountDownLatch 是调用await()方法的线程会被阻塞住,而CyclicBarrier是在构造函数时,第二个参数Runnable会被阻塞住
- CyclicBarrier在使用时,要保证线程数与state的初始变量一致
public class MyCyclicBarrier {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(2);
//CountDownLatch的使用
test01();
//CyclicBarrier的使用
// test02(service);
}
private static void test01() {
CountDownLatch latch = new CountDownLatch(3);
ExecutorService service = Executors.newFixedThreadPool(4);
service.submit(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(1);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + latch.getCount());
});
service.submit(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(1.5);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + latch.getCount());
});
service.submit(() -> {
MyTool.printTimeAndThread("begin...");
MyTool.sleep(2);
latch.countDown();
MyTool.printTimeAndThread("end...{}" + latch.getCount());
});
service.submit(()->{
try {
MyTool.printTimeAndThread("waiting...");
latch.await();
MyTool.printTimeAndThread("waiting end...");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
service.shutdown();
}
private static void test02(ExecutorService service) {
CyclicBarrier barrier = new CyclicBarrier(2,
// 当barrier里state的值为0时就会执行该线程里的内容
()-> {
MyTool.printTimeAndThread("task1, task2 finish...");
});
for (int i = 0; i < 3; i++) { // task1 task2 task1
service.submit(() -> {
MyTool.printTimeAndThread("task1 begin...");
MyTool.sleep(1);
try {
// 会将barrier里此时的变量 -1
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
service.submit(() -> {
MyTool.printTimeAndThread("task2 begin...");
MyTool.sleep(2);
try {
// 会将barrier里此时的变量 -1
barrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
}
service.shutdown();
}
}