线程池应用及实现原理剖析

本文深入探讨Java线程池的工作原理,包括线程池的创建、管理及运行机制。介绍了线程池的主要组件,如线程池管理器、工作线程、任务接口和任务队列。同时,详细解析了不同类型的线程池,如单线程池、固定大小线程池、缓存线程池和调度线程池,以及它们各自的适用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、为什么要使用线程池
在一些需要使用线程去处理任务的业务场景中,如果每一个任务都创建一个线程去处理,任务处理完过后,把这个线程销毁,这样会产生大量的线程创建、销毁的资源开销,Java中更是如此,虚拟机将试图跟踪每一个对象。以便可以在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能降低创建和销毁对象的次数。使用线程池能够有效的控制这种线程的创建和销毁,而且能够对创建的线程进行有效的管理。
使用线程池的好处:
1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
2.提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
3.提高线程的可管理性。线程时稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一分配、调优和监控。

二、Java线程池相关API介绍
1.Executor接口
主要是用来执行提交的任务。下面是接口定义:

public interface Executor{
void exectue(Runnable command);
}

线程池会实现这个接口,并且使用exectue方法来提交一个任务。

2.ExectuorSevice接口
ExecutorService接口是Executor接口的一个子接口,它在Executor接口的基础上增加了一些方法,用来支持对任务的终止管理以及对异步任务的支持。

public interface ExecutorService extends Executor {
    void shutdown();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
    <T> Future<T> submit(Callable<T> task);
    <T> Future<T> submit(Runnable task, T result);
    Future<?> submit(Runnable task);
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws 
    InterruptedException;
    <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks,
                     long timeout, TimeUnit unit)
        throws InterruptedException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks)
        throws InterruptedException, ExecutionException;
    <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                    long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

3.AbstractExecutorService抽象类
AbstractExecutorService实现了ExcutorService,并且基于模板方法模式对一些方法给出了实现。是后面提到的线程池类ThreadPoolExcutor的直接父类。

4.ThreadPoolExcutor类
ThreadPoolExcutor通常就是我们所说的线程池类,Java的线程池就是用这个类进行创建的。
在分析线程池的运行原理时,也是基于这个类来进行分析。

5.ScheduledExecutorService接口
ScheduledExecutorService接口时ExecutorService子接口,定义了线程池基于任务调度的一些方法。

public interface ScheduledExecutorService extends ExecutorService {
    public ScheduledFuture<?> schedule(Runnable command,
                                       long delay, TimeUnit unit);
    public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay, TimeUnit unit);
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
    public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);
}

6.ScheduledThreadPoolExecutor类
ScheduledThreadPoolExecutor集成了ThreadPoolExecutor类,并且实现了ScheduledExecutorService接口,对任务调度的功能进行了实现。

7.Executors类
Executors可以认为是线程池的工厂类,里面提供了静态方法对线程池进行创建。

三、Java线程池的运行原理

1.线程池的参数属性介绍
核心线程数corePoolSize:核心线程池数量。提交一个任务的时候,会对线程池里面的当前存活线程数量和这个corePoolSize进行比较,不同的情况下会有不同的操作。最大线程数maximumPoolSize:线程池所能创建的线程的最大数量。空闲线程的超时时间keepAliveTime:如果线程池当前的线程数大于corePoolSize,并且这些线程中是有空闲线程的,也就是说这些线程没有在执行任务,那么空闲时间超过keepAliveTime时间,这些线程也会被销毁,指代前线程代数等于corePoolsize,这时即便有空闲线程并且超时了,也不会进行线程销毁。任务队列workQueue:这是一个阻塞队列,用于存储提交的任务。线程工厂threadFactory:线程池会使用这个工厂类来创建线程,用户可以自己实现。任务的拒绝处理handler(RejectedExeutionHandler):在线程数已经达到了最大线程数,而且任务队列也满了以后,提交的任务会使用这个handler来处理,用户也可以自己实现。默认是抛出一个异常RejectedExecutionException。
2.线程池运行原理分析
分析当用户提交一个任务使,线程池内部使如何运行的。
在这里插入图片描述

1.创建一个线程池,在还没有任务提交的时候,默认线程池里面是没有线程的。当然,可以调用prestartCoreThread方法,来预先创建一个核心线程。
2.线程池里面还没有线程或者线程池里面存活的线程数小于核心线程数corePoolSize时,这时对于一个新提交的任务,线程池会创建一个线程去处理提交的任务。当线程池里面存活的线程数小于等于核心线程数corePoolSize时,线程池里面的线程会一直存活着,就算空闲时间超过了keepAliveTime,线程也不会被销毁,而是一直阻塞在那里一直等待任务队列的任务来执行。
3.当线程池里面存活的线程数已经等于corePoolSize了,这时对于一个新提交的任务,会被放进任务队列workQueue排队等待执行。而之前创建的线程并不会被销毁,而是不断的去拿阻塞队列里面的任务,当任务列表为空时,线程会阻塞,直到有任务被放进任务队列,线程拿到任务后继续执行,执行完了以后继续去拿任务,这也是为什么线程池队列要使用阻塞队列。
4.当线程池里面存活的线程数已经等于corePoolSize了,并且任务队列也满了,这里假设maximumPoolSize>corePoolSize(如果等于的话,就直接拒绝了),这时如果再来新的任务,线程池就会继续创建新的线程来处理新任务,直到线程数达到maximumPoolSize,就不会再创建了。这些新创建的线程执行完了当前任务后,再任务队列里面还有任务的时候也不会销毁,而是去任务队列拿任务出来执行。在当前线程数大于corePoolSize过后,线程执行完当前任务,会有一个判断当前线程是否需要销毁的逻辑;如果能从任务队列中拿到任务,那么继续执行,如果拿任务时阻塞(说明队列中没有任务),那超过keepAliveTime时间就直接返回null并且销毁当前线程,直到线程池里面的线程数等于corePoolSize之后才不会进行线程销毁。
5.如果当前线程数达到了maximumPoolSize,并且任务队列也满了,这种情况下还有新的任务过来,那就直接采用拒绝的处理器进行执行,默认的处理器逻辑时抛出一个RejectedExcutionException异常。你也可以指定其他的处理器,或者自定义一个拒绝处理器来实现拒绝逻辑的处理(比如把任务存储起来)。JDK提供了四种拒绝策略处理类;AbortPolicy(抛出一个异常,默认的),DiscardPolicy(直接丢弃任务),DiscardOldestPolicy(丢弃队列里最老的任务,将当前这个任务继续提交给线程池),CallerRunPolicy(交给线程池调用的所在线程进行处理)。

在这里插入图片描述
线程池包含以下四个基本组成部分:
1.线程池管理器(ThreadPool):用于创建并管理线程池。包含创建线程池,销毁线程池,加入新任务;
2.工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态。能够循环的运行
任务。
3.任务接口(Task):每一个任务必须实现的接口,以供工作线程调度任务的运行。它主要规定了任务的入口。任务运行完成后的收尾工作,任务的运行状态等。
4.任务队列(taskQueue):用于存放没有处理的任务。提供一种缓冲机制。

3.常用的几种线程池以及使用场景
1.SingleThreadExecutor:单个线程的线程池
这种线程池主要适用于请求量非常小的场景,或者离线的数据处理等,只需要一个线程
就够了。在持续的请求量比较大的情况下,不要使用这种线程池,单线程处理会使队列
不断变大,最终可能导致内存溢出。
2.FixedThreadPool:固定线程大小线程池
这种线程的额corePoolSize和maximumPoolSize是相等的,keepAliveTime设置为0,队列用的是LinkedBlockingQueue无界队列。适用于流量比较稳定的情况,不会说一段时间突然有大量的流量涌入,导致LinkedBlockingQueue越来越大最后导致内存溢出。
3.CachedThreadPool:按需求创建线程数量线程池
这种线程的corePoolSize=0,maximumPoolSize是Integer.MAX_VALUE,keepAliveTime为60秒,队列使用SynchronousQueue同步队列,这个队列可以理解为没有容量的阻塞队列,只有有别的线程来拿任务时,当前线程才能插入成功,反过来也一样。所以这种线程池任务队列时不存任务的,任务全靠创建新的线程来处理,处理完了以后线程空闲超过60秒就会被自动销毁,所以这种线程池适合有一定高峰流量的场景。但是还是要慎用,如果瞬时流量过高会导致创建的线程过多,直接导致服务所在机器的CPU负载过高,然后卡死,所以使用这种线程池必须指代最高峰时的流量也不会导
致CPU负载过高。
4.ScheduledThreadPoolExecutor:任务调度线程池
可以根据自己的需求,使用单线程调度(SingleThreadExecutor),多线程调度(ScheduledThreadPool)。不过现在使用spring调度比较多,所以开发中比较少用。
5.自定义线程池(推荐使用)
根据实际的一个业务场景,自己new一个ThreadPoolExecutor,参数根据业务场景需要指定合适的参数,比如核心线程数设置多少合适,最大线程数设置多少合适,任务队列设置多大的有界合适,拒绝策略也可以自定义,一般采用离线存储啥的,完全根据业务场景来定制。这样可以保证不会发生无界队列导致内存溢出,也不会导致创建的线程过多而导致机器卡死。

4.线程池关闭
1.shutdown():调用后不允许提交新任务,所有调用之前提交的任务都会执行,等所有任务
执行完,才会真正关闭线程池。
2.shutdownNow():强制关闭。返回还没有执行的task列表,然后不让等待的task执行,尝试
停止正在执行的task。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值