Java多线程基础-11:工厂模式及代码案例之线程池

JUC是 java.util.concurrent 这个包的简写,其中存放了Java并发框架为协调并发任务所提供的一些工具。本文介绍其中的Executors、ThreadPoolExecutor类。


在Java中,xx池的概念是很常见的,比如之前遇到过的常量池、数据库连接池等等。线程池是一种常用的多线程处理方式,它可以重复利用已创建的线程,从而减少线程的创建和销毁开销,并提高程序的性能。

构造一个新的线程开销有些大,因为这涉及与操作系统的交互。如果你的程序中创建了大量的生命期很短的线程,那么不应该把每个任务映射到一个单独的线程,而应该使用线程池(thread pool)。线程池中包含许多准备运行的线程。为线程池提供一个 Runnable,就会有一个线程调用 run 方法。当run 方法退出时,这个线程不会死亡,而是留在池中准备为下一个请求提供服务。 

通俗来说,线程池就是提前把线程准备好。当有任务要执行时,原本我们会直接创建一个线程(从系统申请一个线程的资源),然后让这个线程来执行这个任务。但有了线程池后,创建线程不是直接从系统申请,而是从池子里拿;线程不用了,也是还给池子。

池存在的目的是为了提高效率。线程的创建虽然比进程轻量,但是在频繁创建的情况下,开销也是不可忽略的。

线程池最大的好处就是减少了每次启动、销毁线程的损耗。

其实解决频繁创销线程带来的开销这个问题,可以用线程池,也可以用协程。协程是轻量级的线程,不过Java的标准库还不支持(Java只在第三方库的层面上支持协程)。

目录

一、为什么从线程池中获取线程更高效?

⭐线程池的优点

二、Java标准库中的线程池 ExecutorService

1、Executors执行器类 & 线程池的创建

(1)调用Executors类静态工厂方法创建线程池

(2)总结:在使用连接池时所做的工作

2、工厂模式

3、ThreadPoolExecutor类⭐

4、标准库提供的4种拒绝策略⭐

三、代码实现线程池

1、代码解析

2、实际开发中如何给线程池设置合适的线程数量

四、总结:线程池的执行流程

1. 任务提交

2. 队列处理

3. 线程调度

4. 任务执行

5. 线程回收

6. 线程池关闭


一、为什么从线程池中获取线程更高效?

为什么从池子里拿线程要比从系统创建线程更高效?

这是因为,从人程池拿线程是纯粹的用户态操作;而从系统创建线程,就涉及到用户态和内核态之间的切换(真正的创建是要在内核态完成的)。

用户态、内核态是操作系统中的基本概念。

一个操作系统 = 内核 + 配套的应用程序

内核是操作系统最核心的功能模块的集合,如硬件管理,各种驱动,进程管理,内存管理,文件系统等等。这个内核要给上层的应用程序提供支持。比如在显示器上打印hello的操作: println("hello"),应用程序就要调用系统内核,告诉内核我要进行一个打印字符串操作,内核再通过驱动程序操作显示器,完成上述功能。

然而,同一时刻应用程序可能有很多,但内核始终只有一个。内核又要给这么多程序提供服务,这就导致了有的时候服务不一定那么及时。

想象一个银行的工作场景:

这里的柜台内部就相当于内核态,而大厅相当于用户态。

有个办业务的滑稽大哥和柜台的工作人员说,想办一张银行卡,但却发现没有带身份证复印件。这时,工作人员给了他两个方案:要么自己拿着身份证到大厅的复印机复印,要么把身份证交给工作人员,由工作人员在柜台内的复印机复印。

这两种方式在效率上是有差别的:

1、如果自己去复印,快去快回,复印完之后立即就回来了。

2、如果由工作人员复印,他可能去复印的同时还顺便干点别的,比如喝口水,上个厕所,完成一下上级给的别的任务,和同事唠唠嗑……最终确实也能给滑稽大哥复印,但可能就没那么及时了。

总结起来就是,纯用户态的操作,时间是可控的;但涉及到内核态操作,时间就不太可控了。

⭐线程池的优点

  1. 降低资源消耗:减少线程的创建和销毁带来的性能开销。

  2. 提高响应速度:当任务来时可以直接使用,不用等待线程创建。

  3. 可管理性: 进行统一的分配,监控,避免大量的线程间因互相抢占系统资源导致的阻塞现象。


二、Java标准库中的线程池 ExecutorService

ExecutorService是 Java 中用于管理和执行异步任务的接口,它是一个高级的线程池管理工具,继承自 Executor 接口。ExecutorService 接口定义了一组方法,可以用于提交任务、执行任务、关闭线程池等。

执行任务:

  • execute(Runnable task):执行一个 Runnable 任务。

提交任务:可用下面的方法之一将Runnable或 Callable对象提交给ExecutorService,线程池会在方便的时候尽早执行提交的任务。调用submit 时,会得到一个 🔗Future 对象,可用来得到结果或者取消任务:

  1. Future<T>  submit(Callable<T> task):提交一个 Callable 任务给线程池执行。
  2. Future<T>  submit(Runnable task, T result):提交一个 Runnable 任务给线程池执行。(也生成一个Future,它的 get 方法在完成的时候返回指定的 result 对象。)
  3. Future<?>  submit(Runnable task):返回一个看起来有些奇怪的 Future<?>。可以使用这样一个对象来调用isDone、cancel或isCancelled。但是,get 方法在完成的时候只是简单地返回 null。

使用完一个线程池时,调用:

  1. shutdown():启动线程池的关闭序列,平缓地关闭线程池,被关闭的执行器不再接受新的任务,允许已经提交的任务执行完毕。当所有任务都完成时,线程池中的线程死亡。
  2. shutdownNow():立即关闭线程池,尝试中断正在执行的任务,取消所有尚未开始的任务。

通过使用 ExecutorService,我们可以将任务提交给线程池,由线程池自动分配和执行任务。线程池会管理线程的创建、复用和销毁,使多线程任务的执行更高效可控。

1、Executors执行器类 & 线程池的创建

执行器(Executors)类有许多静态工厂方法,用来构造线程池。这些方法返回 ExecutorService 接口 或 ScheduledExecutorService 接口的实例,即线程池实例。ExecutorService 也提供了一些提交任务和管理线程池的方法,如submit()shutdown()等。

(1)调用Executors类静态工厂方法创建线程池

使用 Executors执行器类 可以方便地创建和管理线程池,实现多线程编程以及控制线程的执行方式、数量和生命周期。

以下是创建线程池的代码示例:创建好线程池后,通过pool.submit()方法,向线程池中注册任务。

//创建线程池
ExecutorService pool = Executors.newFixedThreadPool(10);
//添加任务到线程池中
pool.submit(() -> {
    System.out.println("hello");
});
  • <
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值