线程池的作用

减少创建线程的开销(时间开销)。

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

这种具有一般性的目的使得线程池的使用非常广泛(同样的连接池也具有这种一般性)。比如:

  • tomcat 处理请求,加快请求的响应速度
  • 后端处理多线程任务,比如多个互不相关的定时任务

除此之外,线程池还能够限制某个特定功能对线程使用的数量,避免在意外的情况下某个功能耗尽所有的线程资源,可见 分布式(微服务) (kicey.site),其中的舱壁隔离便可以使用线程池完成。

原生线程池

方法 描述
newCachedThreadPool 必要时创建新线程;空闲线程会保留 60 秒
newFixedThreadPool 池中包含固定数目的线程;空闲线程会一直保留
newWorkStrealingPool 一种适合 "fork-Join" 任务的线程池, 其中复杂的任务会分解为更简单的任务,空闲线程会 “密取” 较简单的任务
newSingleThreadExecutor 只有一个线程的线程池,会顺序地执行所提交的任务
newScheduledThreadPool 用千调度执行的固定线程池
newSingleThreadScheduledExecutor 用千调度执行的单线程池

原生线程池任务

Runable

Runable 是不需要返回值的任务,一般使用 ExecutorService.execute(Runable) 加入线程池任务队列。

Callable

Callable 是具有返回值的任务,因为需要交给线程池执行而无法及时的得到结果,需要一个指向结果的引用:Future 类,其中还封装了一些其他的方法,单主要作用还是代表一个还未获取的执行结果。一般使用 Future ExecutorService.submit(Callable) 执行,在提交时返回一个指向结果的 Future 引用。


线程池的使用还有其他一些有趣的应用,比如可以称作单机 map reduce 的 fork-join。

工作原理

以下内容为转载,转载声明如下

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。本文链接:https://blog.csdn.net/magi1201/article/details/115215058

Java线程池主要用于管理线程组及其运行状态,以便Java虚拟机更好的利用CPU资源。Java线程池的工作原理为:JVM先根据用户给定的参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果正在运行的线程数量超过了最大线程数量(用户设置的线程池大小),则超出数量的线程排队等候,在有任务执行完毕后,线程池调度器会发现有可用的线程,进而再次从队列中取出任务并执行。

线程池的主要作用是线程复用、线程资源管理、控制操作系统的最大并发数,以保证系统高效(通过线程资源复用实现)且安全(通过控制最大线程并发数实现)地运行。

线程复用

在Java中,每个Thread类都有一个start方法。在程序调用start方法启动线程时,Java虚拟机会调用该类的run方法。在Thread类的run方法中其实调用了Runnable对象的run方法,吟慈可以继承Thread类,在start方法中不断循环调用传递进来的Runnable对象,程序就会不断执行run方法中的代码。可以将再循环方法中不断获取的Runnable对象放在Queue中,当线程在获取下一个Runnable对象之前是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。

线程池的核心组件和核心类

Java线程池主要由以下4个核心组件组成

  • 线程池管理器:用户创建并管理线程池
  • 工作线程:线程池中执行具体任务的线程
  • 任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,线程中的任务才能够被线程池调度。
  • 任务队列:存放待处理的任务,新的任务将会不断被加入队列中,执行完成的任务将被从队列中移除。

Java中的线程池是通过Executor框架实现的,在该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask这几个核心类。其中tahreadPoolExecutor是构建线程的核心方法,方法定义如下

ThreadPoolExecutor构造函数的具体参数如下

    public ThreadPoolExecutor(int corePoolSize,
    						int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue,
                            ThreadFactory threadFactory,
                            RejectedExecutionHandler handler) 
                            {...}

线程池的工作流程

Java线程池的工作流程为:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照以下流程执行任务。

  • 如果正在运行的线程数量少于corePoolSize(用户定义的核心线程数),线程池就会立刻创建线程并执行该线程任务
  • 如果正在运行的线程数量大于等于corePoolSize,该任务就将被放入阻塞队列中
  • 在阻塞队列已满且正在运行的线程数量少于maximumPoolSize时,线程池会创建非核心线程立刻执行该线程任务
  • 在阻塞队列已满且正在运行的线程数量大于等于maximumPoolSize时,线程池将拒绝执行该线程任务并抛出RejectException异常
  • 在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行
  • 在非核心线程处于空闲状态的时间超过keepAliveTime时间时,正在运行的线程数量超过corePoolSize,该非核心线程将会被认定为空闲线程并停止。因此,在线程池中所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小

线程池的拒绝策略

若线程池中的核心线程数被用完且阻塞队列已满,则此时线程池的线程资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务。jdk内置的拒绝策略有AbortPolicy、CallerRunsPolicy、DiscardOldestPolicy、DiscardPolicy这4种,默认的拒绝策略在ThreadPoolExecutor中作为内部类提供。

1、AbortPolicy 直接抛出异常,组织线程正常运行

2、CallerRunsPolicy  被拒绝的任务,由调用 execute method 方法的线程来执行(比如 main 方法)

3、DiscardOldestPolicy 移除线程队列中最早的一个线程任务,并尝试提交当前任务

4、DiscardPolicy 丢弃当前的线程任务而不做任何处理。