什么是线程池

Java中的线程池是运用场景最多的并发框架,几乎所有需要异步或并发执行任务的程序都可以使用线程池。

在开发过程中,合理地使用线程池能够带来3个好处。
第一:降低资源消耗。通过重复利用机制已降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控。但是,要做到合理利用线程池,必须对其实现原理了如指掌。

线程池作用

线程池是为突然大量爆发的线程设计的,通过有限的几个固定线程为大量的操作服务,减少了创建和销毁线程所需的时间,从而提高效率。

如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。

线程池原理剖析

1.如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会创建一个线程去执行这个任务;

2.如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;

3.如果队列已经满了,则在总线程数不大于maximumPoolSize的前提下,则创建新的线程

4.如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;

5.如果线程池中的线程数量大于 corePoolSize时,如果某线程空闲时间超过keepAliveTime,线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

线程池的拒绝策略:当最大线程数 + 队列缓存数量 小于线程数量的时候,程序运行出错,被拒绝。

线程池的分类

ThreadPoolExecutor

Java是天生就支持并发的语言,支持并发意味着多线程,线程的频繁创建在高并发及大数据量是非常消耗资源的,因为java提供了线程池。在jdk1.5以前的版本中,线程池的使用是及其简陋的,但是在JDK1.5后,有了很大的改善。JDK1.5之后加入了java.util.concurrent包,java.util.concurrent包的加入给予开发人员开发并发程序以及解决并发问题很大的帮助。这篇文章主要介绍下并发包下的Executor接口,Executor接口虽然作为一个非常旧的接口(JDK1.5 2004年发布),但是很多程序员对于其中的一些原理还是不熟悉,因此写这篇文章来介绍下Executor接口,同时巩固下自己的知识。如果文章中有出现错误,欢迎大家指出。

Executor框架的最顶层实现是ThreadPoolExecutor类,Executors工厂类中提供的newScheduledThreadPool、newFixedThreadPool、newCachedThreadPool方法其实也只是ThreadPoolExecutor的构造函数参数不同而已。通过传入不同的参数,就可以构造出适用于不同应用场景下的线程池,那么它的底层原理是怎样实现的呢,这篇就来介绍下ThreadPoolExecutor线程池的运行过程。

查询服务器的核心数:

int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();

 线程池参数

1.corePoolSize

线程池核心线程数,默认情况下,核心线程会在线程池中一直存活,即使它们处于闲置状态,当线程池中的线程数目达到 corePoolSize后,新来的任务将会被添加到缓存队列中,也就是那个workQueue。如果ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔由keepAliveTime所指定的时长后,核心线程就会被终止。

2.maximumPoolSize

线程池中的最大线程数。表示线程池中最多可以创建多少个线程,很多人以为它的作用是这样的:”当线程池中的任务数超过 corePoolSize 后,线程池会继续创建线程,直到线程池中的线程数小于maximumPoolSize“,其实这种理解是完全错误的。它真正的作用是:当线程池中的线程数等于 corePoolSize 并且 workQueue 已满,这时就要看当前线程数是否大于 maximumPoolSize,如果小于maximumPoolSize 定义的值,则会继续创建线程去执行任务, 否则将会调用去相应的任务拒绝策略来拒绝这个任务。另外超过 corePoolSize的线程被称做"Idle Thread", 这部分线程会有一个最大空闲存活时间(keepAliveTime),如果超过这个空闲存活时间还没有任务被分配,则会将这部分线程进行回收。

corePoolSize(核心线程数)与maximumPoolSize(最大线程数) 区别

核心线程数:实际运行的线程数;最大线程数:线程池最多创建的线程数量。corePoolSize<=corePoolSize

3.keepAliveTime

非核心线程空闲存活时长,超过这个时长,非核心线程就会被回收。这个非核心线程就是上面提到的超过 corePoolSize 后新创建的那些线程,默认情况下,只有当线程池中的线程数大于corePoolSize,且这些"idle Thread"并没有被分配任务时,这个参数才会起作用。另外,如果调用了 ThreadPoolExecutor#allowCoreThreadTimeOut(boolean) 的方法,在线程池中的线程数不大于corePoolSize,且这些core Thread 也没有被分配任务时,keepAliveTime 参数也会起作用。

当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于非核心线程。

4.unit

参数keepAliveTime的时间单位,共7种取值,在TimeUtil中定义:

TimeUnit.DAYS;              //天
TimeUnit.HOURS; //小时
TimeUnit.MINUTES; //分钟
TimeUnit.SECONDS; //秒
TimeUnit.MILLISECONDS; //毫秒
TimeUnit.MICROSECONDS; //微妙
TimeUnit.NANOSECONDS; //纳秒

5.workQueue

阻塞队列。如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到该队列当中,注意只要超过了 corePoolSize 就会把任务添加到该缓存队列,添加可能成功也可能不成功,如果成功的话就会等待空闲线程去执行该任务,若添加失败(一般是队列已满),就会根据当前线程池的状态决定如何处理该任务(若线程数 < maximumPoolSize 则新建线程;若线程数 >= maximumPoolSize,则会根据拒绝策略做具体处理)。此队列仅保持由 execute 方法提交的 Runnable 任务。

1) ArrayBlockingQueue       //基于数组的先进先出队列,此队列创建时必须指定大小;
2)LinkedBlockingQueue      //基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为Integer.MAX_VALUE;
3)synchronousQueue        //这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。

 6.threadFactory

线程工厂,用来为线程池创建线程,当我们不指定线程工厂时,线程池内部会调用 ​Executors.defaultThreadFactory()​创建默认的线程工厂,其后续创建的线程优先级都是 ​​Thread.NORM_PRIORITY​。如果我们指定线程工厂,我们可以对产生的线程进行一定的操作。

7.rejectHandler

拒绝执行策略。当线程池的缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

ThreadPoolExecutor.AbortPolicy:         // 丢弃任务并抛出RejectedExecutionException异常。
ThreadPoolExecutor.DiscardPolicy:       // 也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:   // 丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:     // 由调用线程处理该任务

 

线程池常用方法

execute():提交任务,交给线程池执行

submit():提交任务,能够返回执行结果 execute + Future

shutdown():关闭线程池,等待任务都执行完

shutdownNow():关闭线程池,不等待任务执行完

getTaskCount():线程池已执行和未执行的任务总数

getCompletedTaskCount():已完成的任务数量

getPoolSize():线程池当前的线程数量

getActiveCount():当前线程池中正在执行任务的线程数量

线程池四种创建方式

Java通过Executors(jdk1.5并发包)提供四种线程池,分别为:

newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:

1     /**
2 * @ClassName: NewCachedThreadPool
3 * @description: 创建一个可缓存的线程池 可创建线程数是无限大小的
4 * @author: mingtian
5 * @Date:2019/5/2 12:18
6 **/
7 public class NewCachedThreadPool {
8 public static void main(String[] args) {
9 // 可缓存线程池 Executors表示启动线程的 可创建线程数是无限大小的
10 ExecutorService executorService = Executors.newCachedThreadPool();
11 for (int i = 0; i < 10; i++) {
12 final int temp = i;
13 // 可执行线程 execute 启动线程
14 executorService.execute(new Runnable() {
15 public void run() {
16 System.out.println(Thread.currentThread().getName() + "," + temp);
17 }
18 });
19 }
20 //停止线程池
21 executorService.shutdown();
22
23 }
24 }

 

总结: 线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。 

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。示例代码如下:

 

1 /**
2 * @ClassName: NewFixedThreadPool
3 * @description: 创建可固定长度的线程池
4 * @author: mingtian
5 * @Date:2019/5/2 12:27
6 **/
7 public class NewFixedThreadPool {
8 public static void main(String[] args) {
9 //创建可固定长度的线程池,只会创建3个线程池进行处理
10 ExecutorService executorService = Executors.newFixedThreadPool(3);
11 for (int i = 0; i < 20; i++) {
12 final int temp = i;
13 // 可执行线程 execute 启动线程
14 executorService.execute(new Runnable() {
15 public void run() {
16 System.out.println(Thread.currentThread().getName() + "," + temp);
17 }
18 });
19 }
20 //停止线程池
21 executorService.shutdown();
22 }

 

定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()总结:因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行。延迟执行示例代码如下:

 

1 /**
2 * @ClassName: NewScheduledThreadPool
3 * @description: 创建可定时执行的线程池 延迟多久执行线程
4 * @author: mingtian
5 * @Date:2019/5/2 12:35
6 **/
7 public class NewScheduledThreadPool {
8 public static void main(String[] args) {
9 //创建可定时执行的线程池
10 ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
11 for (int i = 0; i < 10; i++) {
12 final int temp=i;
13 //schedule 方法表示线程执行 表示延迟3秒之后 开始执行线程
14 scheduledExecutorService.schedule(new Runnable() {
15 public void run() {
16 System.out.println(Thread.currentThread().getName()+""+temp);
17 }
18 }, 3, TimeUnit.SECONDS);
19 }
20 //停止线程池
21 scheduledExecutorService.shutdown();
22 }

 

newSingleThreadExecutor表示延迟3秒执行。

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。示例代码如下:

 

1 /**
2 * @ClassName: NewSingleThreadExecutor
3 * @description: 单例线程池
4 * @author: mingtian
5 * @Date:2019/5/2 12:45
6 **/
7 public class NewSingleThreadExecutor {
8 public static void main(String[] args) {
9 //单线程
10 ExecutorService executorService = Executors.newSingleThreadExecutor();
11 for (int i = 0; i < 10; i++) {
12 final int temp = i;
13 // 可执行线程 execute 启动线程
14 executorService.execute(new Runnable() {
15 public void run() {
16 System.out.println(Thread.currentThread().getName() + "," + temp);
17 }
18 });
19 }
20 //停止线程池
21 executorService.shutdown();
22 }

自定义线程池

方法一:

1 /**
2 * @Description: 线程池配置
3 *


4 * CPU密集型:核心线程数 = CPU 核数 + 1
5 * IO密集型:核心线程数 = CPU 核数 * 2
6 * @Author: mingtian
7 * @CreateDate: 2020/11/12 9:59
8 * @Version: 1.0
9 */
10 public class ThreadPoolUtil {
11 /**
12 * 默认 CPU 核心数
13 */
14 private static int threadPoolSize = 0;
15
16 static {
17 // 获取服务器 CPU 核心数
18 threadPoolSize = Runtime.getRuntime().availableProcessors();
19 System.out.println(" CPU 核心数量:" + threadPoolSize);
20 }
21
22 public static int getThreadPoolSize() {
23 return threadPoolSize;
24 }
25
26 /**
27 * 线程工厂,用来创建线程
28 */
29 private static ThreadFactory build = new ThreadFactoryBuilder().setNameFormat("sendMessage-pool-%d").build();
30
31 /**
32 * 设置线程池核心参数(IO 密集型) 核心线程数 = CPU 核数 * 2
33 */
34 private static ThreadPoolExecutor threadPoolExecutorIO =
35 new ThreadPoolExecutor(threadPoolSize, threadPoolSize * 2, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2000), build, new ThreadPoolExecutor.DiscardOldestPolicy());
36
37 /**
38 * 设置线程池核心参数(CPU 密集型) 核心线程数 = CPU 核数 + 1 // 核心线程数 = CPU 核数 + 1
39 */
40 private static ThreadPoolExecutor threadPoolExecutorCPU =
41 new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(2000), build, new ThreadPoolExecutor.DiscardOldestPolicy());
42
43 /**
44 * 返回线程池对象
45 *
46 * @return
47 */
48 public static ThreadPoolExecutor getThreadPoolExecutorIO() {
49 return threadPoolExecutorIO;
50 }
51
52 /**
53 * 推送消息给 socket 连接的客户端
54 *
55 * @param session
56 * @param userId
57 * @param message
58 */
59 public static void pushMessageToClient(WebSocketSession session, String userId, TextMessage message) {
60 PushMessageService.PushMessageToClient pushMessageToClient = new PushMessageService.PushMessageToClient(session, userId, message);
61 threadPoolExecutorIO.execute(pushMessageToClient);
62 }
63
64 }

 方法二:推荐使用

package com.example.util;

import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.*;

/**
* spring 线程池配置
*
* @author jackson.wang
*/
@EnableAsync
@Configuration
public class TaskExecutorConfig {
/**
* 打印日志
*/
private static final Logger logger = LoggerFactory.getLogger(TaskExecutorConfig.class);

/**
* 默认 CPU 核心数
*/
private static int threadPoolSize = 0;

/**
* 队列大小 可根据业务场景 进行配置
*/
private static final int queueCapacity = 1000;

static {
// 获取服务器 CPU 核心数
threadPoolSize = Runtime.getRuntime().availableProcessors();
logger.info("服务器 CPU 核心数量:{}", threadPoolSize);
}

/**
* 线程工厂,用来创建线程
*/
private static ThreadFactory build = new ThreadFactoryBuilder().setNameFormat("phone-poll-%d").build();

/**
* 设置线程池核心参数(IO 密集型) 核心线程数 = CPU 核数 * 2
*/
@Bean("threadPoolExecutorIO")
public Executor threadPoolExecutorIO() {
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize * 2,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity),
build, new ThreadPoolExecutor.DiscardOldestPolicy());
}

/**
* 设置线程池核心参数(CPU 密集型) 核心线程数 = CPU 核数
*/
@Bean("threadPoolExecutorCPU")
public Executor threadPoolExecutorCPU() {
return new ThreadPoolExecutor(threadPoolSize, threadPoolSize, 60L,
TimeUnit.SECONDS, new LinkedBlockingQueue<>(queueCapacity), build,
new ThreadPoolExecutor.DiscardOldestPolicy());
}
}

 

 CPU 密集 与 IO 密集博客地址:​

 参考微信文章:​​https://mp.weixin.qq.com/s/yvAMHSKjfjACWGKIA8ubPw​