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
    评论
第8章 多线程编程 第1页 本章概述 本章的学习目标 主要内容 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第1页。 本章概述 前面我们所开发的程序大多是线程的,即一个程序只有一条从头到尾的执行路线。然而,现实世界中的很多事务都是有多种途径同时运作的,例如:服务器可能需要同时处理多个客户机的请求,这就需要有多个线程同时在工作。多线程编程使得系统资源并不是由某个执行体独占,而是由多个执行元共同拥有,轮换使用。正确使用多线程可以消除系统的瓶颈问题,提高整个应用系统的性能。本章将详细介绍Java语言的多线程技术。通过本章的学习,读者应该理解线程和进程的区别,掌握Java的多线程编程技术,了解线程的同步和线程间通信等内容。 第2页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第2页。 本章的学习目标 了解进程和线程的基本概念和区别 掌握创建线程的两种方法 掌握线程同步的概念和方法 了解线程的优先级 掌握线程间通信的方法 第3页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第3页。 第4页 主要内容 8.1 Java线程模型 8.2 创建线程 8.3 同步与线程间通信 8.4 获取线程状态 8.5 本章小结 8.6 思考和练习 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第4页。 8.1 Java线程模型 Java对多线程编程(multithreaded programming)提供了内置支持。多线程程序包含同时运行的两个或多个部分。这种程序的每一部分被称为一个线程,并且每个线程定义了独的执行路径。因此,多线程是特殊形式的多任务处理。 第5页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第5页。 进程 进程本质上是正在执行的程序。在UNIX操作系统中,每个应用程序的执行都在操作系统内核中登记一个进程标志,操作系统根据分配的标志对应用程序的执行进行调度和系统资源分配。每个进程都有自己的内存元,进程之间是互相独立的,一个进程一般不允许访问其他进程的内存空间,因此,进程间通信非常困难。 基于进程的多任务处理就是允许计算机同时运行两个或更多个程序的特性。例如,基于进程的多任务处理可以在运行Java编译器的同时使用文本编辑器或浏览网站。在基于进程的多任务处理中,程序是调度程序能够调度的最小代码元。 第6页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第6页。 线程 线程是比进程更小的执行位。如果将进程概念一分为二,则进程中的系统资源,可以看成是一个静态的对象;而程序代码的执行位置,可以看成一个动态对象,这个动态的部分就是线程。进程在执行过程中拥有独立的内存元,而多个线程共享内存,线程之间的通信比较容易解决,从而极大地提高了程序的运行效率。 在基于线程的多任务环境中,最小的可调度代码元是线程,这意味着个程序可以同时执行两个或更多个任务。例如,文本编辑器可以在打印的同时格式化文本,只要这两个动作是通过两个独立的线程执行即可。 第7页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第7页。 进程和线程的区别 进程和线程的区别可以总结为如下几点: 一个程序至少有一个进程,一个进程至少有一个线程,线程是进程的一个实体,是CPU调度和分派的基本位,它是比进程更小的能独立运行的基本位。 线程在执行过程中与进程也是有区别的,每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口;但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程的执行控制。 一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发执行。 第8页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第8页。 Java中的线程 Java运行时系统在许多方面依赖于线程,并且所有类库在设计时都考虑了多线程。事实上,Java通过利用线程使得整个环境能够异步执行。 在过去几年,多核系统已经变得很普遍了。当然,核系统仍然在广泛使用。Java的多线程系统在这两种类型的系统中都可以工作。在核系统中,并发执行的线程共享CPU,每个线程得到一片CPU时钟周期。所以,在核系统中,两个或更多个线程不是真正同时运行的,但是空闲时间被利用了。然而,在多核系统中,两个或多个线程可能是真正同步执行的。 第9页 Java程序设计案例教程-第8章-多线程编程全文共36页,当前为第9页。 线程的状态 线程有多种状态:线程可以处于运行(running)状态,只要获得CPU时间就准备运行。运行的线程可以被挂起(suspended),这会临时停止线程的活动。挂起的线程可以被恢复(resumed),从而允许线程从停止处恢复执行。当等待
《Linux多线程服务端编程:使用muduo C++网络库》主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread。 目 录 第1部分C++ 多线程系统编程 第1章线程安全的对象生命期管理3 1.1当析构函数遇到多线程. . . . . . . . . . . . . . . . .. . . . . . . . . . . 3 1.1.1线程安全的定义. . . . . . . . . . . . . . . . .. . . . . . . . . . . 4 1.1.2MutexLock 与MutexLockGuard. . . . . . . . . . . . . . . . . . . . 4 1.1.3一个线程安全的Counter 示例.. . . . . . . . . . . . . . . . . . . 4 1.2对象的创建很简. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 5 1.3销毁太难. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 7 1.3.1mutex 不是办法. . . . . . . . . . . . . . . . . . . .. . . . . . . . 7 1.3.2作为数据成员的mutex 不能保护析构.. . . . . . . . . . . . . . 8 1.4线程安全的Observer 有多难.. . . . . . . . . . . . . . . . . . . . . . . . 8 1.5原始指针有何不妥. . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 11 1.6神器shared_ptr/weak_ptr . . . . . . . . . .. . . . . . . . . . . . . . . . 13 1.7插曲:系统地避免各种指针错误. . . . . . . . . . . . . . . . .. . . . . . 14 1.8应用到Observer 上.. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 1.9再论shared_ptr 的线程安全.. . . . . . . . . . . . . . . . . . . . . . . . 17 1.10shared_ptr 技术与陷阱. . . .. . . . . . . . . . . . . . . . . . . . . . . . 19 1.11对象池. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . . 21 1.11.1enable_shared_from_this . . . . . . . . . . . . . . . . . . . . . . 23 1.11.2弱回调. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . 24 1.12替代方案. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . . 26 1.13心得与小结. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . . 26 1.14Observer 之谬. . . .. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 第2章线程同步精要 2.1互斥器(mutex). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 2.1.1只使用非递归的mutex . . . . . . . . . . . . . .. . . . . . . . . . 33 2.1.2死锁. . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . . . . 35 2.2条件变量(condition variable). . . . . . . . . .
好的,我可以为您提供Java编程异步多线程和同步多线程的例子。 首先,让我们来看看Java编程中异步多线程的例子: ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class AsyncThreadExample { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> { // 异步线程执行的操作 System.out.println("异步线程执行了该操作"); }); executor.shutdown(); } } ``` 在上面的代码中,我们使用Java的Executor框架创建了一个线程的线程池对象executor。然后我们使用submit()方法提交了一个异步执行的任务,该任务使用lambda表达式的形式定义。最后我们关闭了线程池。 现在,让我们来看看Java编程中同步多线程的例子: ```java public class SyncThreadExample { public static void main(String[] args) { Thread thread1 = new Thread(new MyRunnable()); Thread thread2 = new Thread(new MyRunnable()); thread1.start(); thread2.start(); try { thread1.join(); thread2.join(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("所有线程都已完成"); } private static class MyRunnable implements Runnable { @Override public void run() { synchronized (this) { // 同步执行的代码块 System.out.println("线程" + Thread.currentThread().getName() + "正在执行该操作"); try { Thread.sleep(500); // 并让线程睡眠500毫秒 } catch (InterruptedException e) { e.printStackTrace(); } } } } } ``` 在上面的代码中,我们创建了两个线程thread1和thread2,并在主线程中启动它们。在MyRunnable类的run()方法中,我们使用synchronized关键字锁定了当前线程,使线程同步执行其中的代码块。 接着,我们使用join()方法等待所有线程完成执行,最后输出所有线程都已完成。 希望这个例子对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值