Java 线程池-详细介绍-通俗易懂

一、为什么使用线程池

​ 我们了解到如何去创建线程,但是如果我们每一次都去创建线程。我们是否回去想,既然是创建线程我们为什么不能像连接池一样呢。做到线程之间的复用呢,减少资源之间的浪费呢?

​ JDK为我们提供了多种线程池技术。通过Executors提供五种线程池,都是直接或间接继承自ThreadPoolExcecutor 线程池类。

优势:

  • 减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
  • 可以根据系统的承受能力,调整线程池中工作线程的数目,防止消耗过多的内存
  • 项目应该创建统一的线程池,如静态或者交给容器处理,而不是每回都去 new 一个线程池

二、创建线程池

创建线程池有两种方式:

1、通过Executors

Executors类提供了4种不同的线程池:

  • newFixedThreadPool

​ 创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

  • newCachedThreadPool

​ 创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

  • newScheduledThreadPool

​ 创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

  • newSingleThreadExecutor

​ 创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

newCachedThreadPool示例:

public class ExecutorsDemo {

  // 创建线程池
    private static ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
 
    public static void main(String[] args) {
        cachedThreadPool.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("this is runnable");
            }
        });
    }
    
}
2、通过ThreadPoolExecutor构造方法

Executors中创建线程池的快捷方法,实际上是调用了ThreadPoolExecutor的构造方法(定时任务使用的是ScheduledThreadPoolExecutor),该类构造方法参数列表如下:

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, 				 // 线程池长期维持的线程数,即使线程处于空闲状态,也不会回收。
  int maximumPoolSize, 				 // 线程数的上限
  long keepAliveTime, TimeUnit unit, // 超过corePoolSize的线程的空闲时长,
                                     // 超过这个时间,多余的线程会被回收。
  BlockingQueue<Runnable> workQueue, // 任务的排队队列
  ThreadFactory threadFactory, 		 // 新线程的产生方式
  RejectedExecutionHandler handler   // 拒绝策略
) 

1、corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

2、maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

3、keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

4、workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

最常见的3种队列类型:

  • 直接交换:SynchronousQueue(没有队列作为缓存)

  • 无界队列:LinkedBlockingQueue(不限制队列大小,如果处理速度低于列队添加的速度,会浪费内存)

  • 有界的队列:ArrayBlockingQueue(队列容量满了后,才会创建新的线程)

5、threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

6、handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

三、线程池的工作流程

举个例子:

​ 以运营一家装修公司做个比喻。公司在办公地点等待客户来提交装修请求;公司有固定数量的正式工以维持运转;旺季业务较多时,新来的客户请求会被排期,比如接单后告诉用户一个月后才能开始装修;当排期太多时,为避免用户等太久,公司会通过某些渠道(比如人才市场、熟人介绍等)雇佣一些临时工(注意,招聘临时工是在排期排满之后);如果临时工也忙不过来,公司将决定不再接收新的客户,直接拒单。

线程池就是程序中的“装修公司”,代劳各种脏活累活。上面的过程对应到线程池上:

// Java线程池的完整构造函数
public ThreadPoolExecutor(
  int corePoolSize, 				 // 正式工数量
  int maximumPoolSize, 				 // 工人数量上限,包括正式工和临时工
  long keepAliveTime, TimeUnit unit, // 临时工游手好闲的最长时间,超过这个时间将被解雇
  BlockingQueue<Runnable> workQueue, // 排期队列
  ThreadFactory threadFactory, 		 // 招人渠道
  RejectedExecutionHandler handler   // 拒单方式
)

线程池流程:

img

1、判断核心线程池是否已满,没满则创建一个新的工作线程来执行任务。已满则。
2、判断任务队列是否已满,没满则将新提交的任务添加在工作队列,已满则。
3、判断整个线程池是否已满,没满则创建一个新的工作线程来执行任务,已满则执行饱和策略。

四、Runnable和Callable

可以向线程池提交任务有两种方式:RunnableCallable,二者的区别如下:

  1. 方法签名不同,void Runnable.run(), V Callable.call() throws Exception
  2. 是否允许有返回值,Callable允许有返回值
  3. 是否允许抛出异常,Callable允许抛出异常。

Callable是JDK1.5时加入的接口,作为Runnable的一种补充,允许有返回值,允许抛出异常。

三种提交任务的方式:

提交方式是否关心返回结果
Future<T> submit(Callable<T> task)
void execute(Runnable command)
Future<?> submit(Runnable task)否,虽然返回Future,但是其get()方法总是返回null

五、常见的拒绝策略

线程池给我们提供了几种常见的拒绝策略:
undefined

拒绝策略拒绝行为
AbortPolicy抛出RejectedExecutionException
DiscardPolicy什么也不做,直接忽略
DiscardOldestPolicy丢弃执行队列中最老的任务,尝试为当前提交的任务腾出位置
CallerRunsPolicy直接由提交任务者执行这个任务

线程池默认的拒绝行为是AbortPolicy,也就是抛出RejectedExecutionHandler异常,该异常是非受检异常,很容易忘记捕获。如果不关心任务被拒绝的事件,可以将拒绝策略设置成DiscardPolicy,这样多余的任务会悄悄的被忽略。

ExecutorService executorService = new ThreadPoolExecutor(
    			2, 2, 
				0, TimeUnit.SECONDS, 
				new ArrayBlockingQueue<>(512), 
				new ThreadPoolExecutor.DiscardPolicy() // 指定拒绝策略
				);

六、线程池状态

  • RUNNING:接受新任务并处理排队任务
  • SHUTDOWN:不接受新任务,单处理排队任务
  • STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务(shutdownNow)
  • TIDYING:所有任务都已终止,(worderCount)任务数为0,线程会转到TIDYING状态,并执行terminate()钩子方法
  • TERMINATED:terminate()运行完成

参考:

https://www.cnblogs.com/CarpenterLee/p/9558026.html

https://blog.csdn.net/wxssbsb/article/details/115529613

https://blog.csdn.net/xufei5789651/article/details/120399432

https://www.cnblogs.com/fysola/p/6076118.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值