Java 单线程池、多线程池、同步、异步的实践

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


知识点:
线程池线程池: Executors.newSingleThreadExecutor()
多线程线程池: ThreadPoolTaskExecutor
一个可取消的异步线程: FutureTask

一、 需求:

假设:提供一个打印文件的服务:外围控制器负责发起打印请求,服务负责打印数据,打印数据有两种模式,关心打印顺序不关心打印顺序两种,其中调用方只关心消息指令是否下发成功,不关心执行结果存在并发

需求分析:

1.调用方只关心指令下发是否成功,不关心执行结果:可考虑异步,在接收到指令时立即返回“收到指令”,具体业务的执行过程所需要的时间;
2.并发:使用多线程的思想;
3.方法执行的顺序问题:可从同步执行、或独占线程的角度去考虑;

涉及内容:单线程线程池、多线程线程池、

//一个可取消的异步线程
FutureTask<Boolean> futureTask = new FutureTask<>();//使用 futureTask.get() 会等待线程的结果,将异步的方法变成同步
//单一线程池:只开启一个线程,所有任务排队顺序执行
ExecutorService executor = Executors.newSingleThreadExecutor();
//多线程线程池:同时开启多个线程,所有任务被分配不同线程异步执行
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

二、 解决方案

Java 线程池、同步、异步实践

1.开启一个ApiController,接收外围系统触发打印信息;
2.开启一个线程池,异步处理,触发方法后立马给返回消息;
3.内部执行过程交由主线程去处理,且分别实现线程的顺序执行和异步执行;

三、 基本模块

1.定义线程池配置,处理异步请求

//AsyncConfig.class
/**
 * 线程池的配置
 * 放入线程池的线程并发执行
 *
 *@author HeYQ
 *@date 2022/5/16
 */
@Configuration
//@EnableAsync
public class AsyncConfig {
    //接收报文核心线程数
    //@Value("${book.core.poolsize}")//后续可以定义在yml配置文件中
    private int bookCorePoolSize = 20;
    //接收报文最大线程数
    //@Value("${book.max.poolsize}")
    private int bookMaxPoolSize = 20;
    //接收报文队列容量
    //@Value("${book.queue.capacity}")
    private int bookQueueCapacity = 20;
    //接收报文线程活跃时间(秒)
    //@Value("${book.keepAlive.seconds}")
    private int bookKeepAliveSeconds = 100;
    //接收报文默认线程名称
    //@Value("${book.thread.name.prefix}")
    private String bookThreadNamePrefix = "print_recv";

    /**
     * 接口的线程池
     *
     * @return TaskExecutor taskExecutor接口
     * @since JDK 1.8
     */
    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor() {
        //newFixedThreadPool
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();//多线程线程池,内部线程是异步执行的
        // 设置核心线程数:一般线程池会至少保持这么多的线程数量
        executor.setCorePoolSize(bookCorePoolSize);
        // 设置最大线程数:线程池最多有这么多的线程数量
        executor.setMaxPoolSize(bookMaxPoolSize);
        // 设置队列容量
        executor.setQueueCapacity(bookQueueCapacity);
        // 设置线程活跃时间(秒)
        executor.setKeepAliveSeconds(bookKeepAliveSeconds);
        // 设置默认线程名称
        executor.setThreadNamePrefix(bookThreadNamePrefix);
        // 设置拒绝策略
        // rejection-policy:当pool已经达到max size的时候,如何处理新任务
        // CALLER_RUNS:不在新线程中执行任务,而是由调用者所在的线程来执行
        //AbortPolicy:用于被拒绝任务的处理程序,它将抛出RejectedExecutionException
        //CallerRunsPolicy:用于被拒绝任务的处理程序,它直接在execute方法的调用线程中运行被拒绝的任务。
        //DiscardOldestPolicy:用于被拒绝任务的处理程序,它放弃最旧的未处理请求,然后重试execute。
        //DiscardPolicy:用于被拒绝任务的处理程序,默认情况下它将丢弃被拒绝的任务。
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        // 等待所有任务结束后再关闭线程池
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.initialize();
        return executor;
    }
}

2.定义单线程池,处理顺序执行的异步请求

//SyncConfig.class
/**
 * 单线程池配置
 * 放入线程池的线程顺序执行
 *
 * @author HeYQ
 * @date 2022/5/16
 */
@Configuration
public class SyncConfig {
    @Bean(name = "singleThreadPool")
    public ExecutorService singleThreadPoolExecutor() {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        return executor;
    }
}

3.定义线程,执行具体的工作

//PrintTask.java
/**
 * 测试时使用的例子
 * 调用该方法,创建一个线程,该现场可以传参,内部可以调用指定的方法
 *
 * @author HeYQ
 * @date 2022/5/16
 */
@Slf4j
@Component
//@Async
public class PrintTask implements Callable<Boolean> {
    //操作类型
    private String operationType;

    public PrintTask() {
    }

    //增加有参构造方法,使得创建线程时可以传值
    public PrintTask(String operationType) {
        this.operationType = operationType;
    }

    @Override
    public synchronized Boolean call() throws Exception {
        String curId = PrimaryGenerator.getInstance().nextFormNo("");//自己的方法,获取一个主键ID,也可以自己写
        int a = 0;
        while (a < 100) {
            a++;
            log.info(this.operationType + curId + "开始执行" + "第" + a + "次");
            Thread.sleep(500);
        }
        return true;
    }
}

4.定义接口API,往线程池中创建任务

//忽略我Post传的数据对象,实际只需要能触发就行
@Log4j
@RestController
@RequestMapping(path = "/Print")
public class AsyncPrintController {

    @Resource
    ThreadPoolTaskExecutor threadPoolTaskExecutor;//多线程池
    @Resource
    ExecutorService singleThreadPoolExecutor;//单线程池
    
    /**
     * 多线程打印:不要求方法执行的顺序
     *
     * @author HeYQ
     */
    @RequestMapping(value = "/asyncPrin", method = RequestMethod.POST)
    @ResponseBody
    public Result printHtml(@RequestBody LabelInfoDto tempInfo) throws Exception {
        try {
            Assert.isFalse(tempInfo.getTemplateType().equals(null), "打印失败,条码类型不能为空!");
            FutureTask<Boolean> futureTask = new FutureTask<>(new PrintTask("printHtml"));
            threadPoolTaskExecutor.submit(futureTask);
            return Result.success("接收信息成功");
        } catch (Exception ex) {
            log.error("打印时出现异常" + ex.getMessage());
            return Result.success("接收信息时出现异常:" + ex.getMessage());
        }
    }

    /**
     * 单线程打印:关心打印顺序
     *
     * @author HeYQ
     */
    @RequestMapping(value = "/syncPrin", method = RequestMethod.POST)
    @ResponseBody
    public Result printTest(@RequestBody LabelInfoDto tempInfo) throws Exception {
        try {
            Assert.isFalse(tempInfo.getTemplateType().equals(null), "打印失败,条码类型不能为空!");
            FutureTask<Boolean> futureTask = new FutureTask<>(new PrintTask("printTest"));
            singleThreadPoolExecutor.submit(futureTask);
            //1.方法不会等待线程执行完,会立马返回 "接收信息成功"
            //log.error("拿到结果" +  futureTask.get());
            //2. futureTask.get() 会等待线程的结果,将异步的方法变成同步
            return Result.success("接收信息成功");
        } catch (Exception ex) {
            log.error("打印测试时出现异常" + ex.getMessage());
            return Result.success("时出现异常:" + ex.getMessage());
        }
    }
}

四、测试结果

1.异步打印:不需要关心打印顺序,会存在交叉打印的情况
调用:
在这里插入图片描述

结果:调用了两次,两个线程穿插执行
在这里插入图片描述

2.同步打印:接口在调用时就得到返回值,内部方法交由线程池顺序执行,打印连续,不会存在穿插情况
在这里插入图片描述
结果:调用了两次,第二次会在第一次完全打印完之后才会执行
在这里插入图片描述

总结

以上就是今天要讲的内容,本文仅仅简单介绍了使用线程池实现了异步调用,并通过单线程线程池和多线程线程池的使用,实现了对线程内方法顺序的控制。
补充: FutureTask futureTask = new FutureTask<>(); 的使用。


//1.singleThreadPoolExecutor 为线程池
//2.创建PrintTask线程,定义为可取消的任务
 FutureTask<Boolean> futureTask = new FutureTask<>(new PrintTask("printTest"));
//3.将线程交由线程池托管
singleThreadPoolExecutor.submit(futureTask);
//4.方法不会等待线程执行完,会立马返回  "接收信息成功"
//log.error("拿到结果" +  futureTask.get());
//5. 如果使用  futureTask.get() 会等待线程的结果,将异步的方法变成同步
 return Result.success("接收信息成功");

可参看该片文章:彻底理解Java的Future模式

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值