快速使用需求:我不要理解一堆理论想直接用 操作说明
ITask.java
PutEsTask.java
TaskExecutor.java
TaskQueue.java
TestMain.java
请把这几个类文件复制下去,运行testMain的方法,根据TestMain的运行日志,【1】-> 【8】不同需求的逻辑需求,组装你的一步多线程队列即可。
精解需求:场景带入
快速理解小场景描述:
【一群人】来到【一个大厅】办理业务,大厅中有【多个窗口】给我们办理业务。
每个人都有自己要办事情,处理过程需要消耗时间。
大厅根据人群多少,开始窗口梳理。
如果把“一群人”理解成一群待处理的n个【任务】,把这群人排成一个长队就形成了一个【任务队列】,“多个窗口”充当我们的【多个线程】异步处理任务队列。我们多线程解决任务队列的代入感来了,有木有!
“大厅”用来充当线程和任务的组装以及处理关系。如:大厅营业start:所有窗口等待办公创建多线程,大厅stop:所有窗口关闭,回收线程。
接下来,就是多个线程异步处理队列任务的干货!
任务类接口
1.首先使我们的任务接口
把这个任务设计成接口,为了后续我们使用的方便宜行,后续去实现接口,我们可以是发短信任务,发邮件任务,等待下载任务等等。
文件:ITask.java
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 <T>
* @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){
}
}
}
样例测试
4.1.控制器中根据业务逻辑生成任务队列,创建多线程实现异步任务队列
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
闭包动态控制各个环节任务
文件 TestMain.java
5.控制器中根据业务逻辑生成任务队列,创建多线程实现异步任务队列
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执行带有回收机制图形:
间隔20s无回收调用图形:
小结
小总结:
如果这个异步线程调用比较频繁,如我们最后这两张图的展示间隔频率20s,此时在JC机制和JVM还没来得及自行回收线程,我们的下一次调用线程已经出发,会造成我们的线程出线性增长,这样就可能是一个比较危险的操作。条件允许,自行要把进程收回!小波浪稳定性线形图,肯定要比折线增长的安全!
具体主动回收,还是等待机制自己处理,这个需要看我们实际应用的业务场景!
至此结束!
- 多线程
多线程(multithreading),是指从软件或者硬件上实现多个线程并发执行的技术。具有多线程能力的计算机因有硬件支持而能够在同一时间执行多于一个线程,进而提升整体处理性能。具有这种能力的系统包括对称多处理机、多核心处理器以及芯片级多处理或同时多线程处理器。在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理” 。
在计算机编程中,一个基本的概念就是同时对多个任务加以控制。许多程序设计问题都要求程序能够停下手头的工作,改为处理其他一些问题,再返回主进程。可以通过多种途径达到这个目的。最开始的时候,那些掌握机器低级语言的程序员编写一些“中断服务例程”,主进程的暂停是通过硬件级的中断实现的。尽管这是一种有用的方法,但编出的程序很难移植,由此造成了另一类的代价高昂问题。中断对那些实时性很强的任务来说是很有必要的。但对于其他许多问题,只要求将问题划分进入独立运行的程序片断中,使整个程序能更迅速地响应用户的请求 。
最开始,线程只是用于分配单个处理器的处理时间的一种工具。但假如操作系统本身支持多个处理器,那么每个线程都可分配给一个不同的处理器,真正进入“并行运算”状态。从程序设计语言的角度看,多线程操作最有价值的特性之一就是程序员不必关心到底使用了多少个处理器。程序在逻辑意义上被分割为数个线程;假如机器本身安装了多个处理器,那么程序会运行得更快,毋需作出任何特殊的调校。根据前面的论述,大家可能感觉线程处理非常简单。但必须注意一个问题:共享资源!如果有多个线程同时运行,而且它们试图访问相同的资源,就会遇到一个问题。举个例子来说,两个线程不能将信息同时发送给一台打印机。为解决这个问题,对那些可共享的资源来说(比如打印机),它们在使用期间必须进入锁定状态。所以一个线程可将资源锁定,在完成了它的任务后,再解开(释放)这个锁,使其他线程可以接着使用同样的资源。
线程是进程中的一部分,也是进程的的实际运作单位,它也是操作系统中的最小运算调度单位。进程中的一个单一顺序的控制流就是一条线程,多个线程可以在一个进程中并发。可以使用多线程技术来提高运行效率。
多线程是为了同步完成多项任务,不是为了提高运行效率,而是为了提高资源使用效率来提高系统的效率。线程是在同一时间需要完成多项任务的时候实现的 。
- 异步
一、异步和多线程有什么区别?其实,异步是目的,而多线程是实现这个目的的方法。异步是说,A发起一个操作后(一般都是比较耗时的操作,如果不耗时的操作就没有必要异步了),可以继续自顾自的处理它自己的事儿,不用干等着这个耗时操作返回。.Net中的这种异步编程模型,就简化了多线程编程,我们甚至都不用去关心Thread类,就可以做一个异步操作出来。
二、随着拥有多个硬线程CPU(超线程、双核)的普及,多线程和异步操作等并发程序设计方法也受到了更多的关注和讨论。本文主要是想探讨一下如何使用并发来最大化程序的性能