线程池的原理及线程池的创建方式
什么是线程池
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 * <p>
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://www.cnblogs.com/ming-blogs/p/10897242.html