一、初步理解JUC中的CountDownLatch
1、减法计数器。可以用来倒计时,当两个线程同时执行时,如果要确保一个线程优先执行(是优先执行,
不是优先处理完),可以使用计数器,当计数器清零的时候,再让另一个线程执行。比如:下面如何确
保 Thread 的线程比主线程优先输出?
public class Test {
public static void main(String[] args) {
//创建一个 CountDownLatch
CountDownLatch countDownLatch = new CountDownLatch(100); //参数:倒计时数字
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("+++++++++++++++Thread");
countDownLatch.countDown();
}
}).start();
try {
countDownLatch.await(); //当倒计时走完之后,我就唤醒其它线程
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("main-----------------");
}
}
}
输出结果:无论运行多少次,肯定是 Thread 先输出
注意: new CountDownLatch(100)、countDown()、await() 必须配合起来使用,创建对象的时候赋的值是多少,coutDown() 就必须执行多少次,否则计数器是没有清零的,计数器就不会停止,其他线程也无法唤醒,程序就会一直卡在那。所以必须保证计数器清零(确保清零:coutDown() 的调用次数必须大于构造函数的参数值)
二、深入理解JUC中的CountDownLatch
减法计数器。可以用来倒计时,当两个线程同时执行时,如果要确保一个线程优先执行(是优先执行,不是优先处理完),就可以使用计数器,当计数器清零的时候,再让另一个线程执行。
await()方法阻塞
1、await() 方法是CountDownLatch类的一个核心方法,它的作用是阻塞调用它的线程。
2、await() 并不是唤醒线程的操作,而是阻塞线程的一种机制。当一个线程调用 countDownLatch.await() 时,该线程会进入等待状态。只有当 countDown() 减到 0 时,所有在 await() 上等待的线程才会被唤醒,继续往下执行。
案例:假设有一个比赛,10 名运动员在起跑线等待发令枪声响起(所有运动员必须同时开始跑步)。他们跑到终点后,统计最终的成绩。
public class Test {
public static void main(String[] args) {
//创建两个CountDownLatch:
//模拟发令枪(让所有运动员同时起跑)
CountDownLatch readyToStart = new CountDownLatch(1);
//统计所有运动员到达终点(让主线程等待所有运动员跑完)
CountDownLatch raceFinished = new CountDownLatch(10);
//创建线程池用于执行任务
ExecutorService executorService = Executors.newFixedThreadPool(10);
//创建10个运动员
for (int i = 0; i < 10; i++) {
//运动员编号
int athleteNumber = i + 1;
executorService.submit(() -> {
try {
System.out.println("运动员" + athleteNumber + "准备好了,等待发令枪...");
//所有运动员准备好后等待发令枪
readyToStart.await(); // 阻塞在这里,等待发令枪(主线程调用countDown())
System.out.println("运动员" + athleteNumber + "开始跑步!");
// 模拟运动员跑步的过程
Thread.sleep((long) (Math.random() * 1000)); // 跑步时间随机
System.out.println("运动员" + athleteNumber + "到达终点!");
//跑到终点后调用countDown,减少计数器
raceFinished.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
try {
// 让主线程等待几秒模拟运动员准备
Thread.sleep(2000);
System.out.println("所有运动员准备好了,发令枪响!");
// 发令枪响,所有运动员同时起跑
readyToStart.countDown(); // 将readyToStart的计数器减到0,所有await()的线程开始执行
// 主线程等待所有运动员到达终点
raceFinished.await(); // 阻塞主线程,直到raceFinished计数器为0
System.out.println("所有运动员都到达终点,比赛结束!");
// 关闭线程池
executorService.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出结果如下:
运动员1准备好了,等待发令枪…
运动员7准备好了,等待发令枪…
运动员8准备好了,等待发令枪…
运动员6准备好了,等待发令枪…
运动员5准备好了,等待发令枪…
运动员4准备好了,等待发令枪…
运动员3准备好了,等待发令枪…
运动员2准备好了,等待发令枪…
运动员9准备好了,等待发令枪…
运动员10准备好了,等待发令枪…
所有运动员准备好了,发令枪响!
运动员7开始跑步!
运动员8开始跑步!
运动员5开始跑步!
运动员9开始跑步!
运动员2开始跑步!
运动员1开始跑步!
运动员3开始跑步!
运动员4开始跑步!
运动员6开始跑步!
运动员10开始跑步!
运动员5到达终点!
运动员2到达终点!
运动员6到达终点!
运动员10到达终点!
运动员9到达终点!
运动员8到达终点!
运动员7到达终点!
运动员4到达终点!
运动员3到达终点!
运动员1到达终点!
所有运动员都到达终点,比赛结束!
具体的过程如下图所示:

理解之后,下面是多线程模拟高并发的代码,这个是一个常见的高并发场景测试代码框架:
/**
* 多线程模拟高并发
*/
@Slf4j
public class concurrenceTest {
public static void concurrentTest() {
//创建一个原子整形类,0是初始值:所有的线程将同时对这个atomicInteger进行操作。
final AtomicInteger atomicInteger = new AtomicInteger(0);
//创建2个计数器:
//当所有都准备好了,再一起执行,模拟多并发,保证并发量
CountDownLatch countDownLatch = new CountDownLatch(1000);
//保证所有现场执行完之后再打印atomicInteger的值
CountDownLatch countDownLatch1 = new CountDownLatch(1000);
ExecutorService executorService = Executors.newFixedThreadPool(10);
try {
for (int i = 0; i < 1000; i++) {
//线程池会调度这些任务,并发执行
executorService.submit(() -> {
try {
//一直阻塞当前线程,直到提交1000个任务(都准备好了)
countDownLatch.await();
} catch (InterruptedException e) {
log.error(e.getMessage(), e);
}
//每个线程的任务:自增1000
for (int j = 0; j < 1000; j++) {
//下面的代码可以用业务代码替换(这里只是模拟)
atomicInteger.incrementAndGet();
}
//一个任务完成了,计数器-1
countDownLatch1.countDown();
});
//确保1000个任务创建完成
countDownLatch.countDown();
}
//等待所有线程的任务都执行完,然后主线程执行下面的代码
countDownLatch1.await();
log.info("atomicInteger的值为:{}", atomicInteger.get());
executorService.shutdown();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
public static void main(String[] args) {
concurrentTest();
}
}
输出结果:1000 * 1000 = 1,000,000
问题:AtomicInteger是线程安全的,为什么还是1000*1000,而不是1000?
解析:
AtomicInteger是线程安全的。在多线程环境中,AtomicInteger能够保证所有线程在修改它的值时不会发生竞争条件,从而确保数据的一致性。- 并不是因为有 final 修饰才是这样的。代码中的
AtomicInteger是在方法外部初始化的,并且传递给了所有线程,所以每个线程操作的是同一个atomicInteger,不是各自独立的实例。 - 变量共享:在 Java 中,如果一个对象(比如
AtomicInteger)被定义在方法的外部作用域,并传递给多个线程,即使它不是final,这些线程依然会引用同一个对象的内存地址。因此,所有线程都会操作同一个AtomicInteger实例。 final关键字的作用是确保变量不能被重新赋值,而不是控制对象是否在线程间共享。
至此,文章内容结束,主页还有更多干货哦!!!
1221

被折叠的 条评论
为什么被折叠?



