ITask.java
PutEsTask.java
TaskExecutor.java
TaskQueue.java
TestMain.java
请把这几个类文件复制下去,运行testMain的方法,根据TestMain的运行日志,【1】-> 【8】不同需求的逻辑需求,组装你的一步多线程队列即可。
如果把“一群人”理解成一群待处理的n个【任务】,把这群人排成一个长队就形成了一个【任务队列】,“多个窗口”充当我们的【多个线程】异步处理任务队列。我们多线程解决任务队列的代入感来了,有木有!
“大厅”用来充当线程和任务的组装以及处理关系。如:大厅营业start:所有窗口等待办公创建多线程,大厅stop:所有窗口关闭,回收线程。
接下来,就是多个线程异步处理队列任务的干货!
package com.sboot.blog.task;
/**
* 任务的执行体或者携带体 理解成去窗口办事的人
*
* @author zhaoxinglu
*/
public interface ITask {
/**
* 执行体中 自定义任务内容
*/
void run();
}
2.我们的线程类
这里也就是大厅窗口的一个建设,用来处理任务
文件:TaskExecutor.java
主要任务是:管理每个线程对任务的take()的持有
package com.atguigu.gulimall.search.morethread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.BlockingQueue;
/**
* 处理任务的窗口 窗口上班 就位执行体 处理费时的任务
*
*
*/
public class TaskExecutor extends Thread {
/**
* 执行体队列
*/
private BlockingQueue<ITask> taskQueue;
/**
* 窗口当前事务处理状态 初始化默认开启
*/
private boolean isRunning = true;
/**
* 窗口名字 方便观察线程
*/
private String taskName;
public TaskExecutor(BlockingQueue<ITask> taskQueue){
this.taskQueue = taskQueue;
this.taskName = makeName();
}
/**
* 生产窗口名字
*/
public String makeName(){
String str= "abcdefghijklmnopqrstuvwxyzABCDEFGHIKJLMNOPQRSTUVWXYZ";
Random random = new Random();
StringBuffer sb = new StringBuffer();
for(int i=0;i<6;++i){
int number = random.nextInt(52);
sb.append(str.charAt(number));
}
return sb.toString();
}
/**
* 窗口工作状态关闭
*/
public void quit(){
isRunning = false;
interrupt();
}
@Override
public void run(){
while (isRunning){
ITask iTask;
try{
iTask = taskQueue.take();
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS");
System.out.println("【4.0】TaskExecutor类的窗口报:窗口["+taskName+"_"+Thread.currentThread().getName() +"]窗口领取了一个任务,后面还有任务数:"
+ taskQueue.size()
+df.format(new Date())
);
}catch (InterruptedException e){
if(!isRunning){
interrupt();
break;
}
continue;
}
iTask.run();
}
}
}
3.任务队列和多线程的调配
文件:TaskQueue.java
主要功能:初始化窗口数量(线程数)、增加任务队列、查看任务队列长度、窗口(线程)任务开启、窗口(线程)任务关闭
package com.sboot.blog.task;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
/**
* 任务队列
* 控制执行体和处理窗口的任务队列
*
* @author zhaoxinglu
*/
public class TaskQueue {
/**
* 某场景下 排队办事的执行体
*/
private BlockingQueue<ITask> mTaskQueue;
/**
* 某场景下 处理执行体的多个窗口
*/
public TaskExecutor[] mTaskExecutors;
/**
* 创建队列的时候 设定窗口数量
*
* @param size
*/
public TaskQueue(int size) {
mTaskQueue = new LinkedBlockingQueue<>();
mTaskExecutors = new TaskExecutor[size];
}
/**
* 场景开始启动
*/
public void start() {
//防止存在未关闭窗口 如果有先关闭
stop();
//所有窗口状态:等待处理事务
for (int i = 0; i < mTaskExecutors.length; i++) {
//每初始化一个窗口 都让窗口观望当前执行体队列mTaskQueue
mTaskExecutors[i] = new TaskExecutor(mTaskQueue);
mTaskExecutors[i].start();
}
}
/**
* 场景关闭 所有窗口关闭
*/
public void stop() {
if (mTaskExecutors != null) {
for (TaskExecutor taskExecutor : mTaskExecutors) {
if (taskExecutor != null) {
taskExecutor.quit();
}
}
}
}
/**
* 允许执行体添加进来
*
* @param task
* @param
* @return
*/
public <T extends ITask> int add(T task) {
if (!mTaskQueue.contains((task))) {
mTaskQueue.add(task);
}
//返回当前排队的执行体数
return mTaskQueue.size();
}
public int getTaskQueueSize(){
return mTaskQueue.size();
}
}
4.实例化一种任务 这里模拟打印机功能
文件:PutEsTask.java
这是对任务接口的一个实例化,不建议这么引用,建议采纳闭包写法动态视力话任务,并且内置countDownLatch.countDown();
逻辑。这个在后头讲,这里我们先做个样例测试。
package com.atguigu.gulimall.search.morethread;
import java.text.SimpleDateFormat;
import java.util.Date;
public class PutEsTask implements ITask{
@Override
public void run(){
try{
/**
* 放置任务有重写 这段逻辑被重写 就不再执行
*/
Thread.sleep(2000);
// System.out.println("执行体:"+ Thread.currentThread().getName());
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println("任务报:当前【"+Thread.currentThread().getName()+"】带我执行任务PutES working!"+ df.format(new Date()));
}catch (InterruptedException ignored){
}
}
}
package com.atguigu.gulimall.search.morethread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class TestMain {
public static void main(String[] args) throws Exception {
/**
* 单纯的异步多线程
*/
testTask();
}
/**
* 单纯的异步多线程
*/
public static void testTask(){
//设置线程数量
TaskQueue taskQueue = new TaskQueue(4);
taskQueue.start();
System.out.println("工作窗口数:"+taskQueue.mTaskExecutors.length);
for(int i = 1 ;i<=10;i++) {
PutEsTask task = new PutEsTask();
taskQueue.add(task);
}
}
}
测试示例结果:
Connected to the target VM, address: '127.0.0.1:60067', transport: 'socket'
工作窗口数:4
【4.0】TaskExecutor类的窗口报:窗口[ynYFjh_Thread-2]窗口领取了一个任务,后面还有任务数:62023-06-06 11:17:31:426
【4.0】TaskExecutor类的窗口报:窗口[aHVPVK_Thread-0]窗口领取了一个任务,后面还有任务数:62023-06-06 11:17:31:426
【4.0】TaskExecutor类的窗口报:窗口[iHlchE_Thread-1]窗口领取了一个任务,后面还有任务数:62023-06-06 11:17:31:426
【4.0】TaskExecutor类的窗口报:窗口[JOnnih_Thread-3]窗口领取了一个任务,后面还有任务数:62023-06-06 11:17:31:426
任务报:当前【Thread-2】带我执行任务PutES working!2023-06-06 11:17:33
【4.0】TaskExecutor类的窗口报:窗口[ynYFjh_Thread-2]窗口领取了一个任务,后面还有任务数:52023-06-06 11:17:33:438
任务报:当前【Thread-3】带我执行任务PutES working!2023-06-06 11:17:33
任务报:当前【Thread-0】带我执行任务PutES working!2023-06-06 11:17:33
【4.0】TaskExecutor类的窗口报:窗口[JOnnih_Thread-3]窗口领取了一个任务,后面还有任务数:32023-06-06 11:17:33:439
任务报:当前【Thread-1】带我执行任务PutES working!2023-06-06 11:17:33
【4.0】TaskExecutor类的窗口报:窗口[aHVPVK_Thread-0]窗口领取了一个任务,后面还有任务数:32023-06-06 11:17:33:439
【4.0】TaskExecutor类的窗口报:窗口[iHlchE_Thread-1]窗口领取了一个任务,后面还有任务数:22023-06-06 11:17:33:441
任务报:当前【Thread-0】带我执行任务PutES working!2023-06-06 11:17:35
任务报:当前【Thread-3】带我执行任务PutES working!2023-06-06 11:17:35
任务报:当前【Thread-2】带我执行任务PutES working!2023-06-06 11:17:35
任务报:当前【Thread-1】带我执行任务PutES working!2023-06-06 11:17:35
【4.0】TaskExecutor类的窗口报:窗口[aHVPVK_Thread-0]窗口领取了一个任务,后面还有任务数:02023-06-06 11:17:35:450
【4.0】TaskExecutor类的窗口报:窗口[JOnnih_Thread-3]窗口领取了一个任务,后面还有任务数:02023-06-06 11:17:35:450
任务报:当前【Thread-3】带我执行任务PutES working!2023-06-06 11:17:37
任务报:当前【Thread-0】带我执行任务PutES working!2023-06-06 11:17:37
package com.atguigu.gulimall.search.morethread;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;
public class TestMain {
public static void main(String[] args) throws Exception {
/**
* 单纯的异步多线程
*/
// testTask();
/**
* 异步多线程
* 附有队列结束回收资源或者其他逻辑
* 设置线程数量
*/
TaskQueue taskQueue = new TaskQueue(4);
taskQueue.start();
System.out.println("工作窗口数(异步线程数):"+taskQueue.mTaskExecutors.length);
//等待执行完毕后回收多线程资源
CountDownLatch countDownLatch = new CountDownLatch(10);
countDownLatch.getCount();
for(int i = 1 ;i<=10;i++){
PutEsTask task = new PutEsTask();
// PutEsTask task = new PutEsTask();
int n = taskQueue.add(new PutEsTask() {
@Override
public void run() {
try{
Thread.sleep(2000);
System.out.println("【4.1】执行体:"+ Thread.currentThread().getName()
+ " countDownLatch:"+ countDownLatch.getCount()
);
countDownLatch.countDown();
}catch (InterruptedException ignored){
System.out.println(ignored.getMessage());
}
}
});
System.out.println("【1】放置每个执行体的操作"+i+" 个时 队列内:" +n);
}
/**
* 这个try的核心【countDownLatch.await()】
* 异步多线程投放任务队列瞬间完成 countDownLatch.getCount() 为队列长度
* 这个try就一直等待队列逻辑 countDownLatch.countDown() --至0
* 在执行【countDownLatch.await()】 后面的逻辑
*
*/
System.out.println("【2】【完成队列投放后,马上执行逻辑】");
try {
System.out.println("【3】阻塞前逻辑 :"+ " countDownLatch:"+ countDownLatch.getCount());
countDownLatch.await();
System.out.println("【5】阻塞后:回收进程区域:"+ " countDownLatch:"+ countDownLatch.getCount());
/**
* 代码体 【多线程回收前】
* 在异步队列结束后的逻辑操作代码区
*/
/*------*/
System.out.println("【6】【队列结束后,多线程回收前 多线程存活状态】");
taskQueue.stop();
/**
* 代码体 【多线程回收后】
* 在异步队列结束后的逻辑操作代码区
*/
/*------*/
System.out.println("【7】【队列结束后,多线程回收前 多线程回收完毕】");
//System.out.println("回收进程区域完毕 确认 stop 窗口数:"+taskQueue.mTaskExecutors.length);
}catch (InterruptedException e){
e.printStackTrace();
}
/**
* 投放队列后完马上执行
*/
System.out.println("【8】整个队列完整执行后 ");
}
/**
* 单纯的异步多线程
*/
public static void testTask(){
TaskQueue taskQueue = new TaskQueue(4);
taskQueue.start();
System.out.println("工作窗口数:"+taskQueue.mTaskExecutors.length);
for(int i = 1 ;i<=10;i++) {
PutEsTask task = new PutEsTask();
taskQueue.add(task);
}
}
}
闭包动态全控示例测试
Connected to the target VM, address: '127.0.0.1:60299', transport: 'socket'
工作窗口数(异步线程数):4
【1】放置每个执行体的操作1 个时 队列内:1
【1】放置每个执行体的操作2 个时 队列内:1
【1】放置每个执行体的操作3 个时 队列内:1
【1】放置每个执行体的操作4 个时 队列内:2
【1】放置每个执行体的操作5 个时 队列内:3
【1】放置每个执行体的操作6 个时 队列内:4
【1】放置每个执行体的操作7 个时 队列内:5
【1】放置每个执行体的操作8 个时 队列内:5
【1】放置每个执行体的操作9 个时 队列内:6
【1】放置每个执行体的操作10 个时 队列内:7
【2】【完成队列投放后,马上执行逻辑】
【3】阻塞前逻辑 : countDownLatch:10
【4.0】TaskExecutor类的窗口报:窗口[tPwCZX_Thread-3]窗口领取了一个任务,后面还有任务数:62023-06-06 11:18:54:865
【4.0】TaskExecutor类的窗口报:窗口[ToyleP_Thread-2]窗口领取了一个任务,后面还有任务数:62023-06-06 11:18:54:865
【4.0】TaskExecutor类的窗口报:窗口[TQYJuk_Thread-0]窗口领取了一个任务,后面还有任务数:62023-06-06 11:18:54:865
【4.0】TaskExecutor类的窗口报:窗口[aBlIlW_Thread-1]窗口领取了一个任务,后面还有任务数:62023-06-06 11:18:54:865
【4.1】执行体:Thread-1 countDownLatch:10
【4.1】执行体:Thread-3 countDownLatch:10
【4.1】执行体:Thread-0 countDownLatch:10
【4.1】执行体:Thread-2 countDownLatch:10
【4.0】TaskExecutor类的窗口报:窗口[ToyleP_Thread-2]窗口领取了一个任务,后面还有任务数:22023-06-06 11:18:56:873
【4.0】TaskExecutor类的窗口报:窗口[aBlIlW_Thread-1]窗口领取了一个任务,后面还有任务数:22023-06-06 11:18:56:873
【4.0】TaskExecutor类的窗口报:窗口[TQYJuk_Thread-0]窗口领取了一个任务,后面还有任务数:22023-06-06 11:18:56:874
【4.0】TaskExecutor类的窗口报:窗口[tPwCZX_Thread-3]窗口领取了一个任务,后面还有任务数:22023-06-06 11:18:56:873
【4.1】执行体:Thread-1 countDownLatch:6
【4.1】执行体:Thread-2 countDownLatch:6
【4.1】执行体:Thread-0 countDownLatch:6
【4.1】执行体:Thread-3 countDownLatch:5
【4.0】TaskExecutor类的窗口报:窗口[ToyleP_Thread-2]窗口领取了一个任务,后面还有任务数:02023-06-06 11:18:58:879
【4.0】TaskExecutor类的窗口报:窗口[aBlIlW_Thread-1]窗口领取了一个任务,后面还有任务数:02023-06-06 11:18:58:879
【4.1】执行体:Thread-1 countDownLatch:2
【4.1】执行体:Thread-2 countDownLatch:2
【5】阻塞后:回收进程区域: countDownLatch:0
【6】【队列结束后,多线程回收前 多线程存活状态】
【7】【队列结束后,多线程回收前 多线程回收完毕】
【8】整个队列完整执行后
Disconnected from the target VM, address: '127.0.0.1:60299', transport: 'socket'
借助工具:
运用工具 监控线程:
建立8个线程:
我们不主动回收 完全靠JC回收的状态:
小总结:
如果这个异步线程调用比较频繁,如我们最后这两张图的展示间隔频率20s,此时在JC机制和JVM还没来得及自行回收线程,我们的下一次调用线程已经出发,会造成我们的线程出线性增长,这样就可能是一个比较危险的操作。条件允许,自行要把进程收回!小波浪稳定性线形图,肯定要比折线增长的安全!
具体主动回收,还是等待机制自己处理,这个需要看我们实际应用的业务场景!
至此结束!
线程是进程中的一部分,也是进程的的实际运作单位,它也是操作系统中的最小运算调度单位。进程中的一个单一顺序的控制流就是一条线程,多个线程可以在一个进程中并发。可以使用多线程技术来提高运行效率。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的 。
二、随着拥有多个硬线程CPU(超线程、双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想探讨一下如何使用并发来最大化程序的性能