线程池的原理及线程池的创建方式

什么是线程池

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

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

posted @ 2019-03-18 23:24  明天,你好啊  阅读(3655)  评论(0编辑  收藏  举报