首页 > 编程语言 >详细解析Java异步线程处理队列任务工具类以及实战

详细解析Java异步线程处理队列任务工具类以及实战

时间:2023-04-03 13:04:05浏览次数:42  
标签:异步 Java 队列 new 线程 import 窗口 public


场景待入


快速理解小场景描述:


【一群人】来到【一个大厅】办理业务,大厅中有【多个窗口】给我们办理业务。


每个人都有自己要办事情,处理过程需要消耗时间。


大厅根据人群多少,开始窗口梳理。

如果把“一群人”理解成一群待处理的n个【任务】,把这群人排成一个长队就形成了一个【任务队列】,“多个窗口”充当我们的【多个线程】异步处理任务队列。我们多线程解决任务队列的代入感来了,有木有!
“大厅”用来充当线程和任务的组装以及处理关系。如:大厅营业start:所有窗口等待办公创建多线程,大厅stop:所有窗口关闭,回收线程。
接下来,就是多个线程异步处理队列任务的干货!

任务类接口


1.首先使我们的任务接口


把这个任务设计成接口,为了后续我们使用的方便宜行,后续去实现接口,我们可以是发短信任务,发邮件任务,等待下载任务等等。


文件:ITask.java

package com.sboot.blog.task;

/**
 * 任务的执行体或者携带体 理解成去窗口办事的人
 *
 * @author zhaoxinglu
 */
public interface ITask {
    /**
     * 执行体中 自定义任务内容
     */
    void run();
}

线程类

2.我们的线程类
这里也就是大厅窗口的一个建设,用来处理任务
文件:TaskExecutor.class

package com.sboot.blog.task;

import java.util.Random;
import java.util.concurrent.BlockingQueue;

/**
 * 处理任务的窗口 窗口上班就位执行体办事儿
 *
 * @author zhaoxinglu
 */
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 = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        Random random = new Random();
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < 6; ++i) {
            int number = random.nextInt(52);// [0,51)
            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();
                System.out.println("窗口报:" + taskName + "后面还有:" + taskQueue.size() + "下一个来" + taskName);

            } catch (InterruptedException e) {
                if (!isRunning) {
                    // 发生意外了,是下班状态的话就把窗口关闭。
                    interrupt();
                    // 如果执行到break,后面的代码就无效了。
                    break;
                }
                // 发生意外了,不是下班状态,那么窗口继续等待。
                continue;
            }

            // 为这个执行体办事
            iTask.run();
        }
    }

}

任务列表和多线程

3.任务队列和多线程的调配
文件:TaskQueue

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.实例化一种任务 这里模拟打印机功能
文件:PrintTask.java

package com.sboot.blog.task;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 打印执行任务
 *
 * @author zhaoxinglu
 */
public class PrintTask implements ITask {

    private int id;

    public PrintTask(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("干活,等了2s"+Thread.currentThread().getName());
        } catch (InterruptedException ignored) {

        }
        //设置日期格式
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // new Date()为获取当前系统时间
        System.out.println(df.format(new Date()));
        System.out.println("当前的id:" + id);
        System.out.println("wait...");
    }

}

异步多线程

5.控制器中根据业务逻辑生成任务队列,创建多线程实现异步任务队列

package com.sboot.blog.sendmsg.controller;

import com.sboot.blog.task.ITask;
import com.sboot.blog.task.PrintTask;
import com.sboot.blog.task.TaskQueue;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.CountDownLatch;

@Api(value = "sendmsg", tags = "发送信息")
@RestController
@RequestMapping("/send")

@Configuration      //1.主要用于标记配置类,兼备Component的效果。
@EnableScheduling   // 2.开启定时任务
public class SendMessageController {

    @ApiOperation(value = "打印信息value", notes = "打印note")
    @GetMapping(value = "/say")
    //@Scheduled(cron = "0/5 * * * * ?")
//    //或直接指定时间间隔,例如:5秒
//    @Scheduled(fixedRate = 5000)
    public String saything() {
        //设置日期格式
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // new Date()为获取当前系统时间
        System.out.println(df.format(new Date()));
        return "test";
    }

    @ApiOperation(value = "测试窗口队列任务")
    @GetMapping(value = "/test")
    public String test() {
        //场景开启 初始化执行窗口
        TaskQueue taskQueue = new TaskQueue(8);
        taskQueue.start();

        System.out.println("初始化后的窗口length:" + taskQueue.mTaskExecutors.length);
        //其他线程可以调用CountDownLatch的countDown()方法来对CountDownLatch中的数字减一, 当数字被减成0后,所有await的线程都将被唤醒。
        //辅助方法阻塞 阻塞完成后 后续回收线程  这个方法场景:全部场景结束后,有操作 采用。注意 这个 18 队列完成18个后就会执行后面的【等待完成操作事件】 如果你的队列n<18 你等不到【等待完成操作事件】 如果n>18 你完成【等待完成操作事件】 后 还会继续队列。【等待完成操作事件】在后面有标注
        CountDownLatch countDownLatch = new CountDownLatch(18);
        for (int i = 0; i < 180; i++) {
            PrintTask task = new PrintTask(i);
//            int n = taskQueue.add(task);
            int n = taskQueue.add(new ITask() {
                                      @Override
                                      public void run() {
                                          try {
                                              Thread.sleep(2000);
                                              System.out.println("干活,等了2s"+Thread.currentThread().getName());
                                              // 执行下面代码计数器减一
                                              countDownLatch.countDown();
                                          } catch (InterruptedException ignored) {

                                          }
                                      }
                                  });
                    System.out.println("放置第" + i + "个时    队列内人数:" + n);
        }


        try {
          //【等待完成操作事件】 这里的阻塞就是完成等待事件
            countDownLatch.await();
            System.out.println("队列执行完 回收");

		
 			//【关闭业务窗】这里对比 stop()的作用。调用之前,我们的操作窗口,还开着,可以继续往里防止队列处理
			//强调对比一下  至此线程还在跑 9999的这个任务会被执行
            PrintTask task = new PrintTask(9999);
            taskQueue.add(task);
            //线程回收
           taskQueue.stop();
            //【关闭业务窗】这里对比 stop()的作用。调用之后,业务窗口关闭,再添加队列 加不进来了
            System.out.println("stop后窗口数:" + taskQueue.mTaskExecutors.length);
			//接下来任务111111就不回执行了
            PrintTask task1 = new PrintTask(111111);
            taskQueue.add(task1);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }


        System.out.println("放完队列后的后的窗口length:" + taskQueue.mTaskExecutors.length);


        return "任务跑起来了";
    }


}

例子日志

附上异步log:

详细解析Java异步线程处理队列任务工具类以及实战_System

名人指导

在几位资深钻石段位程序员的围观指正讨论下:

详细解析Java异步线程处理队列任务工具类以及实战_开发语言_02


详细解析Java异步线程处理队列任务工具类以及实战_java_03


详细解析Java异步线程处理队列任务工具类以及实战_java_04

探讨一下 如果我们窗口不回收会怎么样 线程状况如何的

工具观察

借助工具:

详细解析Java异步线程处理队列任务工具类以及实战_任务队列_05


运用工具 监控线程:

建立8个线程:

我们不主动回收 完全靠JC回收的状态:

详细解析Java异步线程处理队列任务工具类以及实战_System_06

详细解析Java异步线程处理队列任务工具类以及实战_任务队列_07


我们在任务执行完毕后回收:

详细解析Java异步线程处理队列任务工具类以及实战_任务队列_08


详细解析Java异步线程处理队列任务工具类以及实战_java_09


间隔20s执行带有回收机制图形:

详细解析Java异步线程处理队列任务工具类以及实战_jvm_10


间隔20s无回收调用图形:

详细解析Java异步线程处理队列任务工具类以及实战_任务队列_11

小结

小总结:
如果这个异步线程调用比较频繁,如我们最后这两张图的展示间隔频率20s,此时在JC机制和JVM还没来得及自行回收线程,我们的下一次调用线程已经出发,会造成我们的线程出线性增长,这样就可能是一个比较危险的操作。条件允许,自行要把进程收回!小波浪稳定性线形图,肯定要比折线增长的安全!

具体主动回收,还是等待机制自己处理,这个需要看我们实际应用的业务场景!
至此结束!


标签:异步,Java,队列,new,线程,import,窗口,public
From: https://blog.51cto.com/phor/6166072

相关文章

  • 【】Java Error: Port 9095 was already in use
    问题描述JavaError:Port9095wasalreadyinuse问题原因端口被占用导致解决方案Windsow系统netstat-ano|findstr9090查询到占用9090端口的进程PID为9784。tasklist|findstr9784查询到PID为0=7984的进程打开【任务管理器】->【服务】,将对应应用关闭Lin......
  • 使用线程池和窗口池优化electron
    概念窗口池和线程池是两个不同的概念。窗口池是指在Electron中同时创建多个窗口,并对这些窗口进行管理和维护的机制。窗口池可以帮助开发者更好地管理和控制应用中的窗口,从而提高应用的性能和稳定性。在窗口池中,可以对窗口进行创建、销毁、隐藏、显示等操作,以满足不同的应用场景......
  • Java 缺失的特性:扩展方法
    作者:周密(之叶)什么是扩展方法扩展方法,就是能够向现有类型直接“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改现有类型。调用扩展方法的时候,与调用在类型中实际定义的方法相比没有明显的差异。为什么需要扩展方法考虑要实现这样的功能:从Redis取出包含多个商......
  • Java 缺失的特性:扩展方法
    作者:周密(之叶)什么是扩展方法扩展方法,就是能够向现有类型直接“添加”方法,而无需创建新的派生类型、重新编译或以其他方式修改现有类型。调用扩展方法的时候,与调用在类型中实际定义的方法相比没有明显的差异。为什么需要扩展方法考虑要实现这样的功能:从Redis取出包含多个商品ID......
  • java vue获取当月第一天和最后一天,当前周一和周日
    1,vue前端,通过moment获取当月第一天和最后一天,当前周一和周日letcurrDate=moment(newDate(),"YYYY-MM-DD");varfirstDay=moment(currDate.startOf("month").valueOf()).format('YYYY-MM-DD');//获取该月份第一天的时间戳varendDay=moment(cur......
  • Java-Day-1(Java了解 + DOS)
    Java-Day-1JAVA分辨了解Java创始人之一:詹姆斯·高斯林解释性语言,编译出.class后是有一个解释器的(编译性语言:C/C++——编译后的代码已经是二进制可以由机器直接执行了)面向对象的(oop)健壮的跨平台性的一个.java编译好的.class文件,无需再次编译,便既能在windows上......
  • Java-Day-2(转义字符 + 注释 + 代码规范 + 变量 + 数据类型)
    Java-Day-2常用转义字符代码中只一个\会默认转义(写在“”里)\t:制表位,可以实现对齐功能,可以看作有一个无形表框(上下两行长度相差不大)\n:换行符,仅换代码行的话\\:一个\,想输出"\\"就要输入四个\\'':一个“,字符串里输出双引号\':一个‘\r:一个回车,光标......
  • Java-String的常用方法总结
    一、String类  String类在java.lang包中,java使用String类创建一个字符串变量,字符串变量属于对象。java把String类声明的final类,不能继承。String类对象创建后不能修改,由0或多个字符组成,包含在一对双引号之间。二、String类构造方法  1、publicString()  无参构造方法,用来创......
  • 30.查看锁等待相关的阻塞线程、被阻塞线程信息及相关用户、IP、PORT
    SELECTlocked_table,locked_index,locked_type,blocking_pid,concat(T2.USER,'@',T2.HOST)AS"blocking(user@ip:port)",blocking_lock_mode,blocking_trx_rows_modified,waiting_pid,......
  • 【妙用WebView】鸿蒙元服务中如何使用Java Script的API创建地图
    【关键字】webview地图高德腾讯地图百度地图 【问题背景】开发元服务过程中需要用到地图能力:卡片中显示我的快递位置和我的位置信息;PageAbility中可以打开自定义地图,查询POI点,做路径规划、路径推荐等;查看了高德、百度、华为、腾信地图的后发现,各大厂商对鸿蒙系统的支持能......