面试题:说一下Java开启异步线程的几种方法?

文章目录

  • 整体描述
  • 实现方法
  • 一、注解@Async
    • 1. 添加注解
    • 2. 创建异步方法Service和实现类
    • 3. 调用异步方法
  • 二、AsyncManager
    • 1. 创建AsyncManager类
    • 2. 创建一个耗时的操作类
    • 3. 执行异步操作
  • 三、线程池
    • 1. 创建线程池
    • 2. 创建一个耗时的操作类
    • 3. 执行线程池
  • 总结


整体描述

在java中异步线程很重要,比如在业务流处理时,需要通知硬件设备,发短信通知用户,或者需要上传一些图片资源到其他服务器这种耗时的操作,在主线程里处理会阻塞整理流程,而且我们也不需要等待处理结果之后再进行下一步操作,这时候就可以使用异步线程进行处理,这样主线程不会因为这些耗时的操作而阻塞,保证主线程的流程可以正常进行。
最近在项目中使用了很多线程的操作,在这做个记录。


实现方法

线程的操作,是java中最重要的部分之一,实现线程操作也有很多种方法,这里仅介绍几种常用的。在springboot框架中,可以使用注解简单实现线程的操作,还有AsyncManager的方式,如果需要复杂的线程操作,可以使用线程池实现。下面根据具体方法进行介绍。

一、注解@Async

springboot框架的注解,使用时也有一些限制,这个在网上也有很多介绍,@Async注解不能在类本身直接调用,在springboot框架中,可以使用单独的Service实现异步方法,然后在其他的类中调用该Service中的异步方法即可,具体如下:

1. 添加注解

在springboot的config中添加 @EnableAsync注解,开启异步线程功能

package com.thcb.boot.config;import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;/*** MyConfig** @author thcb*/@Configuration
@EnableAsync
public class MyConfig {// 自己配置的Config
}

2. 创建异步方法Service和实现类

使用service实现耗时的方法

Service类:

package com.thcb.execute.service;import org.springframework.scheduling.annotation.Async;/*** IExecuteService** @author thcb*/
public interface IExecuteService {/*** 一些耗时的操作,使用单独线程处理* 这里就简单写了一个sleep5秒的操作*/@Asyncpublic void sleepingTest();
}

Service实现类:

package com.thcb.execute.service.impl;import com.thcb.execute.service.IExecuteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;/*** ExecuteService业务层处理** @author thcb*/
@Service
public class ExecuteServiceImpl implements IExecuteService {private static final Logger log = LoggerFactory.getLogger(ExecuteServiceImpl.class);@Overridepublic void sleepingTest() {log.info("SleepingTest start");try {Thread.sleep(5000);} catch (Exception e) {log.error("SleepingTest:" + e.toString());}log.info("SleepingTest end");}
}

3. 调用异步方法

这里根据Springboot的框架,在controller层调用,并使用log查看是否时异步结果。

controller:

package com.thcb.boot.controller;import com.thcb.execute.service.IExecuteService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** TestController** @author thcb*/
@RestController
public class TestController {private static final Logger log = LoggerFactory.getLogger(TestController.class);@Autowiredprivate IExecuteService executeService;@RequestMapping("/test")public String test() {return "spring boot";}@RequestMapping("/executeTask")public String executeTask() {log.info("executeTask Start!");executeService.sleepingTest();log.info("executeTask End!");return "executeTask";}
}

在log查看结果:

图片

接口直接返回了executeTask,并log出executeTask End!在5秒之后,log打出SleepingTest end,说明使用了异步线程处理了executeService.sleepingTest的方法。

二、AsyncManager

使用AsyncManager方法,也是SpringBoot框架中带的任务管理器,可以实现异步线程。

1. 创建AsyncManager类

使用AsyncManager首先需要创建一个AsyncManager类,这个在springboot框架中应该也是有的:

/*** 异步任务管理器** @author thcb*/
public class AsyncManager {/*** 操作延迟10毫秒*/private final int OPERATE_DELAY_TIME = 10;/*** 异步操作任务调度线程池*/private ScheduledExecutorService executor = SpringUtils.getBean("scheduledExecutorService");/*** 单例模式*/private AsyncManager() {}private static AsyncManager me = new AsyncManager();public static AsyncManager me() {return me;}/*** 执行任务** @param task 任务*/public void execute(TimerTask task) {executor.schedule(task, OPERATE_DELAY_TIME, TimeUnit.MILLISECONDS);}/*** 停止任务线程池*/public void shutdown() {Threads.shutdownAndAwaitTermination(executor);}
}

2. 创建一个耗时的操作类

这里同样需要创建一个耗时的操作,也是用sleep模拟:

public TimerTask sleepingTest() {return new TimerTask() {@Overridepublic void run() {// 耗时操作try {Thread.sleep(5000);} catch (Exception e) {log.error("SleepingTest:" + e.toString());}}};
}

3. 执行异步操作

使用AsyncManager执行异步操作也比较简单,直接调用即可:

// 异步线程池
AsyncManager.me().execute(sleepingTest());

三、线程池

使用线程池可以设定更多的参数,线程池在网上也有很多详细的介绍,在这我只介绍一种,带拒绝策略的线程池。

1. 创建线程池

创建带有拒绝策略的线程池,并设定核心线程数,最大线程数,队列数和超出核心线程数量的线程存活时间:

/*** 线程池信息: 核心线程数量5,最大数量10,队列大小20,超出核心线程数量的线程存活时间:30秒, 指定拒绝策略的*/
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 30, TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>(20), new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {log.error("有任务被拒绝执行了");}
});

2. 创建一个耗时的操作类

由于线程池需要传入一个Runnable,所以此类继承Runnable,还是用sleep模拟耗时操作。

/*** 耗时操作*/
static class MyTask implements Runnable {private int taskNum;public MyTask(int num) {this.taskNum = num;}@Overridepublic void run() {System.out.println("正在执行task " + taskNum);try {Thread.sleep(4000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("task " + taskNum + "执行完毕");}
}

3. 执行线程池

开启线程池,这里通过一个for循环模拟一下,可以看一下log输出,有兴趣的可以修改一下for循环和sleep的数值,看看线程池具体的操作和拒绝流程。

for (int i = 0; i < 20; i++) {MyTask myTask = new MyTask(i);threadPoolExecutor.execute(myTask);System.out.println("线程池中线程数目:" + threadPoolExecutor.getPoolSize() + ",队列中等待执行的任务数目:" +threadPoolExecutor.getQueue().size() + ",已执行完别的任务数目:" + threadPoolExecutor.getCompletedTaskCount());
}
threadPoolExecutor.shutdown();

总结

在此写一些线程操作需要注意的地方:

  • 线程数量和cpu有关,使用线程时一定要注意线程的释放,否则会导致cpu线程数量耗尽;
  • 使用注解完成的线程操作,不可以在自己的类中实现调用,因为注解最后也是通过代理的方式完成异步线程的,最好时在单独的一个service中写;
  • 线程池最好单独写,使用static和final修饰,保证所有使用该线程池的地方使用的是一个线程池,而不能每次都new一个线程池出来,每次都new一个就没有意义了。

以上就是三种线程池的操作,写的不算很详细,有兴趣的同学可以自己在深入研究一下。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/325526.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法每日一题:在链表中插入最大公约数 | 链表 | 最大公约数

hello&#xff0c;大家好&#xff0c;我是星恒 今天的题目是有关链表和最大公约数的题目&#xff0c;比较简单&#xff0c;核心在于求解最大公约数&#xff0c;我们题解中使用辗转相除法来求解&#xff0c;然后我们会在最后给大家拓展一下求解最大公约数的四个方法&#xff0c;…

技术学习|CDA level I 业务分析方法

业务分析方法有三个主要构成部分&#xff1a;业务指标分析、业务模型分析及业务分析方法。 业务指标分析是发现业务问题的核心方法&#xff1a;用于通用指标和场景指标的计算及分析方法&#xff0c;以及指标体系的设计与应用方法。业务模型是从一系列业务行为中抽象出来的信息…

箭头函数 - JavaScript的新宠儿

&#x1f4e2; 鸿蒙专栏&#xff1a;想学鸿蒙的&#xff0c;冲 &#x1f4e2; C语言专栏&#xff1a;想学C语言的&#xff0c;冲 &#x1f4e2; VUE专栏&#xff1a;想学VUE的&#xff0c;冲这里 &#x1f4e2; CSS专栏&#xff1a;想学CSS的&#xff0c;冲这里 &#x1f4…

企业老旧档案怎么处理?

不管选择何种处理方式处理企业老旧档案&#xff0c;都要先制定一份详细的档案处理计划&#xff0c;明确处理的目标、方式和时间&#xff0c;并确保有足够的人力和物力资源来完成处理工作。 一般来说&#xff0c;常用的企业老旧档案有以下几种方法&#xff1a; 1. 整理归档&…

13. 强化学习编程实验1-在格子世界中寻宝(1)

文章目录 1.实验目的2.任务描述3.任务分析3.1 待求问题是多步决策问题否3.2 问题求解过程是一个马尔科夫决策过程3.3 状态空间S的确定3.4 动作空间A的确定3.5 状态转移概率P的确定3.6 立即回报R的确定3.7 折扣 γ \gamma γ的确定 4. 编程架构4.1 程序中有哪些对象和类4.2 环境…

LC 2807. 在链表中插入最大公约数

2807. 在链表中插入最大公约数 难度 &#xff1a; 中等 题目大意&#xff1a; 给你一个链表的头 head &#xff0c;每个结点包含一个整数值。 在相邻结点之间&#xff0c;请你插入一个新的结点&#xff0c;结点值为这两个相邻结点值的 最大公约数 。 请你返回插入之后的链表…

Scikit-Learn线性回归(五)

Scikit-Learn线性回归五:岭回归与Lasso回归 1、误差与模型复杂度2、范数与正则化2.1、范数2.2、正则化3、Scikit-Learn Ridge回归(岭回归)4、Scikit-Learn Lasso回归1、误差与模型复杂度 在第二篇文章 Scikit-Learn线性回归(二) 中,我们已经给出了过拟合与模型泛化的概念并…

深入理解内存检测工具:一文解析内存检测工具的全貌与使用技巧

C和C是非常不安全且容易出错的编程语言&#xff0c;Address Sanitizer是由Google开发的一种工具&#xff0c;用于检测内存访问错误&#xff0c;如使用后释放&#xff08;use-after-free&#xff09;和内存泄漏。它已集成到GCC版本> 4.8中&#xff0c;可用于C和C代码。Addres…

RabbitMQ快速入门(详细)

RabbitMQ 消息中间件/消息队列 1、消息中间件 1、简介 **消息中间件也可以称消息队列&#xff0c;是指用高效可靠的消息传递机制进行与平台无关的数据交流&#xff0c;并基于数据通信来进行分布式系统的集成。**通过提供消息传递和消息队列模型&#xff0c;可以在分布式环境…

如何配置 VS Code 实现 git 密码免输入

目录 问题描述尝试过的失败方法问题分析最终采用的解决方案&#xff1a;利用 ssh key 提供密码免输入功能安装 git windows 命令工具在windows本地生成 ssh key将公钥安装到 git 服务器第一种方法第二种方法调试方法 参考资料: 问题描述 在 Windows 上&#xff0c;使用 Visual…

Protobuf 编码结构

编码结构 什么是protobuf protocol buffers 是一种语言无关、平台无关、可扩展的序列化结构数据的方法&#xff0c;可用于数据通信协议和数据存储等&#xff0c;它是 Google 提供的一个具有高效协议数据交换格式工具库&#xff0c;是一种灵活、高效和自动化机制的结构数据序列…

JavaScript 基础学习笔记(四):循环语句、while循环、中止循环、无限循环、for 语句

目录 一、循环语句 1.1 while循环 1.2 中止循环 1.3 无限循环 二、综合案例-ATM存取款机 三、for 语句 一、循环语句 1.1 while循环 while : 在…. 期间&#xff0c; 所以 while循环 就是在满足条件期间&#xff0c;重复执行某些代码。 语法&#xff1a; while (条件表…