目录
一、前言
哈喽,各位小伙伴,各位新年好,马上就是正月十五元宵节了,希望大家新年身体健康,昨天是情人节,朋友圈里炸开了花,有的人秀恩爱,有的人秀鲜花,各位肯定没少吃狗粮,今天我给你们写点文章压压惊。
距离上次设计模式系列终结后,小编也在想再写点什么呢?正好前段时间与老友聊天,聊到了在多线程开发时候的多线程计数器,那今天就讲讲我们常见的多线程计数器。在讲多线程之前,先讲几个概念。
什么是线程:线程是一个系统中调度的最小单元,例如一个应用程序,他有很多的进程(功能),每一个功能至少有一个或者多个线程来支撑。
多线程开发的意义:多线程开发是为了高效率的完成处理,例如一个用户数据需要处理10s,但是此时有十个数据,他们之间互相隔离,那此时启动十个线程去异步处理,那么效率自然就变高了。
线程池:使用线程池的目的是为了防止无限制的创建线程导致系统宕机,防止资源过载
二、多线程计数器
什么是多线程计数器:在多线程开发时,一个用来在多线程之间共享的一个对象,是线程安全的,通过调用其对象内部方法可完成对线程执行完成数量的统计。
常见的多线程计数器:
1. CountDownLatch:一个倒计数方式的计数器,支持一个或者多个线程一直等待,只到其他线程完成后再执行,使用后作废,不能循环使用,用来阻塞主线程使用。
2. CyclicBarrier:一个正计数方式的计数器,类似CountDownLatch,但是支持可循环使用,用来阻塞子线程使用。
三、代码演示
1. 创建执行CountDownLatch的CountDownLatchRunnable,用来执行程序过程,执行计数器的减1操作
package com.yang.thread.count;
import java.util.concurrent.CountDownLatch;
/**
* @ClassName CountDownLatchRunnable
* @Description 编写runnable
* @Author IT小白架构师之路
* @Date 2022/2/12 14:09
* @Version 1.0
**/
public class CountDownLatchRunnable implements Runnable{
//处理的数据
private int i;
//计数器
private CountDownLatch count;
CountDownLatchRunnable(int i, CountDownLatch count){
this.i = i;
this.count = count;
}
@Override
public void run(){
System.out.println(String.format("CountDownLatchRunnable:数据为%s的开始处理", i));
//生成随机数,模拟每个线程处理的时间
int second = (int) Math.random() * 10 + 1;
try {
Thread.sleep(second);
}catch (Exception e){
System.out.println(String.format("CountDownLatchRunnable:数据为%s的处理失败", i));
}
System.out.println(String.format("CountDownLatchRunnable:数据为%s的处理成功", i));
//计数器减一
count.countDown();
}
}
2. 创建CountDownLatch的测试类CountDownLatchTest,模拟多线程运行,测试线程计数器
package com.yang.thread.count;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @ClassName CountDownLatchTest
* @Description 线程计数器测试
* @Author IT小白架构师之路
* @Date 2022/2/12 14:11
* @Version 1.0
**/
public class CountDownLatchTest {
public static void main(String[] args) {
//数据为10
int[] array = new int[] {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
//创建核心线程数为10的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(array.length);
//创建线程计数器
CountDownLatch count = new CountDownLatch(array.length);
//循环给线程池执行,执行10个线程处理数据
for (int j = 0; j < array.length; j++) {
int data = array [j];
CountDownLatchRunnable runnable = new CountDownLatchRunnable(data, count);
executor.execute(runnable);
}
System.out.println("所有线程都已经分配完毕,待执行完成");
try {
count.await();
executor.shutdown();
System.out.println("全部执行成功");
}catch (Exception e){
System.out.println("执行失败");
}
}
}
3. 线程计数器CountDownLatch测试结果如下:
所有线程都已经分配完毕,待执行完成
CountDownLatchRunnable:数据为60的开始处理
CountDownLatchRunnable:数据为40的开始处理
CountDownLatchRunnable:数据为100的开始处理
CountDownLatchRunnable:数据为90的开始处理
CountDownLatchRunnable:数据为20的开始处理
CountDownLatchRunnable:数据为70的开始处理
CountDownLatchRunnable:数据为10的开始处理
CountDownLatchRunnable:数据为80的开始处理
CountDownLatchRunnable:数据为30的开始处理
CountDownLatchRunnable:数据为50的开始处理
CountDownLatchRunnable:数据为10的处理成功
CountDownLatchRunnable:数据为50的处理成功
CountDownLatchRunnable:数据为80的处理成功
CountDownLatchRunnable:数据为30的处理成功
CountDownLatchRunnable:数据为40的处理成功
CountDownLatchRunnable:数据为100的处理成功
CountDownLatchRunnable:数据为90的处理成功
CountDownLatchRunnable:数据为70的处理成功
CountDownLatchRunnable:数据为60的处理成功
CountDownLatchRunnable:数据为20的处理成功
全部执行成功
4. 编写CyclicBarrier的执行类,名称为CyclicBarrierRunnable,用来执行阻塞子线程
package com.yang.thread.count;
import java.util.concurrent.CyclicBarrier;
/**
* @ClassName CyclicBarrierRunnable
* @Description
* @Author IT小白架构师之路
* @Date 2022/2/14 15:50
* @Version 1.0
**/
public class CyclicBarrierRunnable implements Runnable {
//处理的数据
private int i;
//计数器
private CyclicBarrier cyc;
CyclicBarrierRunnable(int i, CyclicBarrier cyc){
this.i = i;
this.cyc = cyc;
}
@Override
public void run() {
System.out.println(String.format("CyclicBarrierRunnable:数据为%s的开始处理", i));
//生成随机数,模拟每个线程处理的时间
int second = (int) Math.random() * 10 + 1;
//阻塞子线程
try {
Thread.sleep(second);
System.out.println(String.format("CyclicBarrierRunnable:数据为%s的处理成功", i));
//等待所有子线程一起达到阻塞点
cyc.await();
} catch (Exception e) {
System.out.println(String.format("CyclicBarrierRunnable:数据为%s的处理失败", i));
}
}
}
5.编写线程计数器CyclicBarrier的测试类,模拟多线程环境
package com.yang.thread.count;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @ClassName CyclicBarrierTest
* @Description CyclicBarrier测试类
* @Author IT小白架构师之路
* @Date 2022/2/12 16:12
* @Version 1.0
**/
public class CyclicBarrierTest {
public static void main(String[] args) {
//数据为10
int[] array = new int[] {10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
//创建核心线程数为10的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(array.length);
//定义计数器,定义执行完成后执行的内容
CyclicBarrier cyclicBarrier = new CyclicBarrier(array.length, new Runnable() {
@Override
public void run() {
System.out.println("全部执行成功");
executor.shutdown();
}
});
//循环给线程池执行,执行10个线程处理数据
for (int j = 0; j < array.length; j++) {
int data = array [j];
CyclicBarrierRunnable runnable = new CyclicBarrierRunnable(data, cyclicBarrier);
executor.execute(runnable);
}
System.out.println("所有线程都已经分配完毕,待执行完成");
}
}
6. 线程计数器cyclicBarrier的测试结果如下
所有线程都已经分配完毕,待执行完成
CyclicBarrierRunnable:数据为40的开始处理
CyclicBarrierRunnable:数据为70的开始处理
CyclicBarrierRunnable:数据为80的开始处理
CyclicBarrierRunnable:数据为50的开始处理
CyclicBarrierRunnable:数据为30的开始处理
CyclicBarrierRunnable:数据为90的开始处理
CyclicBarrierRunnable:数据为10的开始处理
CyclicBarrierRunnable:数据为20的开始处理
CyclicBarrierRunnable:数据为100的开始处理
CyclicBarrierRunnable:数据为60的开始处理
CyclicBarrierRunnable:数据为20的处理成功
CyclicBarrierRunnable:数据为30的处理成功
CyclicBarrierRunnable:数据为100的处理成功
CyclicBarrierRunnable:数据为10的处理成功
CyclicBarrierRunnable:数据为40的处理成功
CyclicBarrierRunnable:数据为70的处理成功
CyclicBarrierRunnable:数据为80的处理成功
CyclicBarrierRunnable:数据为60的处理成功
CyclicBarrierRunnable:数据为50的处理成功
CyclicBarrierRunnable:数据为90的处理成功
全部执行成功
四、分析与总结
在代码的示例中,我们可以验证在概念中所描述的情况
1. CountDownLatch采用的是内部变量的递减方式,来完成计数的,在使用完成后,变量变为0,则无法计数使用,await使用在主线程中阻塞,待全部完成后继续执行
2. CyclicBarrier采用的计数方式是,在子线程等待其他所有线程到达阻塞状态,数量等于cyclicBarrier的设置值时,继续执行各个子线程,所以cyclicBarrier可以重复被使用
觉得对您有用的话,的关注下公众号吧(一起交流技术)