Java多线程(十三) Java创建线程池,参数详解

在这里插入图片描述

一、前言

对Java开发来说,线程池是工作中经常用到的工具,掌握线程池技术是成为一名合格Java程序员的必要条件。

二、为什么使用线程池?

Java编程中,常创建线程去执行需要异步执行的功能,如果这些功能很多,不停的创建线程然后销毁线程,是否非常消耗资源的。

项目中用MySQL时,通常会配置一个数据源连接池,例如Durid。执行一条SQL时会用到连接池里的一条连接,执行完会还给连接池。

Java线程池的思想,我感觉和上面连接池很像,是避免反复创建线程对象的高开销,从而提高效率。

三、线程池的基础概念

1. 最大线程数

说到线程池,首先你得有个抽象的概念,它像一个水池。那一个水池,它肯定有大小,或者说深度,因为无限深的水池不存在的。

那么Java线程池同理,它也得设置一个大小,否则线程无限创建,内存和CPU都会消耗100%。

最大线程数它就是指线程池最大能创建的线程数量。

在ThreadPoolExecutor类中,最大线程数是maximumPoolSize属性。

2. 核心线程数

核心线程数是ThreadPoolExecutor类中corePoolSize属性。

上面已经说了,线程池中最大的线程数maximumPoolSize属性。

默认设置下,创建线程池后,线程池中线程数量=0,每来一个任务时,线程池中会创建一个线程来执行该任务。

  • 已创建线程数 < corePoolSize时,即使此时线程池中存在空闲线程,也会创建新线程,这些线程称为核心线程
  • 已创建线程数 == corePoolSize时,又有任务来,会把任务放到阻塞队列中排队。
  • 阻塞队列已满,且已创建线程数<maximumPoolSize,创建非核心线程执行任务。
  • 阻塞队列已满,且已创建线程数==maximumPoolSize,使用拒绝策略

核心线程数 + 非核心线程数 = 最大线程数

所以设置线程池时,要设置corePoolSizemaximumPoolSize这2个参数。

因为通过计算:非核心线程数 = maximumPoolSize - corePoolSize

注意:上面公式的背景是任务超多,已创建线程数达到maximumPoolSize了。

如果 corePoolSize < 已创建线程数 < maximumPoolSize,也就是线程池还没到上限。

那么:非核心线程数 = 已创建线程数 - corePoolSize

下面看下线程池的流程图,可以更好的理解corePoolSizemaximumPoolSize属性。
在这里插入图片描述

3. 线程存活时间

线程存活时间是ThreadPoolExecutor类中keepAliveTime和unit这2个属性共同设置。

keepAliveTime是时间大小,而unit是单位(天、小时、分钟、秒、毫秒、微妙、纳秒)

默认当线程池中已创建线程数 > corePoolSize时,keepAliveTime才会起作用。

即一个空闲线程存活到keepAliveTime了,就会被销毁,直到已创建线程数== corePoolSize

但是如果设置allowCoreThreadTimeOut(true)时,已创建线程数<= corePoolSize,keepAliveTime也会起作用。

4. 阻塞队列

阻塞队列是ThreadPoolExecutor类中workQueue属性。

一个阻塞队列,用来存储等待执行的任务,这个参数会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

  1. ArrayBlockingQueue : 有界的数组队列
  2. LinkedBlockingQueue : 可支持有界/无界的队列,使用链表实现
  3. PriorityBlockingQueue : 优先队列,可以针对任务排序
  4. SynchronousQueue : 队列长度为1的队列,和Array有点区别就是:client thread提交到block queue会是一个阻塞过程,直到有一个worker thread连接上来poll task。

ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

5. 拒绝策略

拒绝策略是ThreadPoolExecutor类中handler属性。

拒绝策略表示当拒绝处理任务时的策略,有以下四种取值:

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

四、Executors工具类创建线程池

在学线程池时,教材和博客上一般都会介绍Executors工具类下面的几种Java已经实现的线程池。

下面定义一个任务类:

public class MyWorker implements Runnable {

    private Integer num;

    public MyWorker() {
    }

    public MyWorker(Integer num) {
        this.num = num;
    }

    @Override
    public void run() {

        System.out.println(Thread.currentThread().getName() + "正在执行,数值:" + num);

        try {
            //假装工作1秒
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1. 可变数量 newCachedThreadPool

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

public class Thread01_NewCached_Runnable {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        
        for (int i=1; i<= 100; i++){
            executorService.submit(new MyWorker(i));
        }
        executorService.shutdown();
    }
}

运行结果:

pool-1-thread-1正在执行,数值:1
pool-1-thread-2正在执行,数值:2
。。。。。。
pool-1-thread-98正在执行,数值:98
pool-1-thread-99正在执行,数值:99
pool-1-thread-100正在执行,数值:100

从结果可见线程池创建了100个线程来执行任务,返回结果也非常快。

我们来看newCachedThreadPool的定义:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}
属性newCachedThreadPool的值
corePoolSize0
maximumPoolSizeInteger.MAX_VALUE
keepAliveTime60
unitTimeUnit.SECONDS
workQueueSynchronousQueue
threadFactorydefaultThreadFactory
handlerAbortPolicy

2. 固定数量 newFixedThreadPool

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

public class Thread03_NewFixed_Runnable {

    public static void main(String[] args) {
    	//固定大小=3的线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        for (int i=1; i<= 100; i++){
            executorService.submit(new MyWorker(i));
        }
        executorService.shutdown();
    }
}

运行结果

pool-1-thread-2正在执行,数值:2
pool-1-thread-3正在执行,数值:3
pool-1-thread-1正在执行,数值:1
pool-1-thread-1正在执行,数值:4
pool-1-thread-3正在执行,数值:5
pool-1-thread-2正在执行,数值:6
pool-1-thread-1正在执行,数值:7
pool-1-thread-2正在执行,数值:9
pool-1-thread-3正在执行,数值:8
pool-1-thread-3正在执行,数值:12
pool-1-thread-2正在执行,数值:10

从结果看出,线程池总共创建了3个线程thread-1,thread-2,thread-3,它们共同完成这100个任务。

因为每个任务耗时1秒,可以肉眼看出,控制台每隔1秒才打印3行,速度比上一个newCachedThreadPool的慢些。

看下newFixedThreadPool的实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
属性newFixedThreadPool的值
corePoolSizenThreads
maximumPoolSizenThreads
keepAliveTime0
unitTimeUnit.MILLISECONDS
workQueueLinkedBlockingQueue 长度最大Integer.MAX_VALUE
threadFactorydefaultThreadFactory
handlerAbortPolicy

3. 固定一个 newSingleThreadExecutor

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

public class Thread04_NewSingle_Runnable {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i=1; i<= 100; i++){
            executorService.submit(new MyWorker(i));
        }
        executorService.shutdown();
    }
}
pool-1-thread-1正在执行,数值:1
pool-1-thread-1正在执行,数值:2
pool-1-thread-1正在执行,数值:3
。。。。。。
pool-1-thread-1正在执行,数值:99
pool-1-thread-1正在执行,数值:100

从结果看线程池里总共就1个线程thread-1,而且任务按照顺序执行,因为每个任务耗时1秒,结果也是每隔1秒打印1行。这个速度和上面的比时最慢的。

看下newSingleThreadExecutor的实现:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

下面看下newSingleThreadExecutor的实现,并和上面2种一起对比看下。

属性newCachednewFixednewSingle
corePoolSize0nThreads1
maximumPoolSizeInteger.MAX_VALUEnThreads1
keepAliveTime6000
unitTimeUnit.SECONDSTimeUnit.MILLISECONDSTimeUnit.MILLISECONDS
workQueueSynchronousQueueLinkedBlockingQueueLinkedBlockingQueue
threadFactorydefaultThreadFactorydefaultThreadFactorydefaultThreadFactory
handlerAbortPolicyAbortPolicyAbortPolicy

注意:LinkedBlockingQueue 最长为Integer.MAX_VALUE

4. 定时线程池 newScheduledThreadPool

指定核心线程数量,普通线程数量无限,线程执行完任务立即回收,任务队列为延时阻塞队列。这是一个比较特别的线程池,适用于执行定时周期性的任务。

public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

定时3秒执行

public class Thread05_NewScheduled_Runnable {

    public static void main(String[] args) {

        ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);

        MyRunnable myRunnable = new MyRunnable();

        for (int i=0; i< 10; i++){
            scheduledThreadPool.schedule(myRunnable, 3, TimeUnit.SECONDS);
        }
        scheduledThreadPool.shutdown();
    }
}

五、为什么阿里巴巴不允许使用Executors

在阿里巴巴的Java开发手册中有一条如下:
在这里插入图片描述
避免使用Executors创建线程池,那么我们可以自己直接调用ThreadPoolExecutor的构造函数来自己创建线程池。在创建的同时,给BlockQueue指定容量。

private static ExecutorService executor = new ThreadPoolExecutor(10, 10,
        60L, TimeUnit.SECONDS,
        new ArrayBlockingQueue(10));

六、自定义线程池,并验证

自定义一个线程池

  • corePoolSize = 2
  • maximumPoolSize = 10
  • 阻塞队列长度 = 2
public class Thread07_MyThreadPool {

    private static ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 10, 1L, TimeUnit.HOURS,
            new ArrayBlockingQueue<Runnable>(2), new ThreadFactory() {
        
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r,r.getClass().getSimpleName());
            return thread;
        }
    });


    public static void main(String[] args) {

        for (int i=1; i<= 13; i++){
            executor.submit(new MyWorker(i));
        }
        executor.shutdown();
    }
}

在每个线程运行都不结束的情况下,创建线程;

创建1、2时,线程开始执行,使用核心线程;

创建3、4时,因核心线程占用,3、4进入阻塞队列等待;

创建5、6、7、8、9、10、11、12时,因核心线程被占,而且等待队列也已满。
非核心线程数 = 最大线程数(10) - 核心线程数(2) = 8个。
那线程池创建8个非核心线程,处理5、6、7、8、9、10、11、12这8个任务。
从而已创建线程数 == 最大线程数

此时:

  • 1,2 在核心线程
  • 3,4 在阻塞队列
  • 5、6、7、8、9、10、11、12 这8个在非核心线程

创建13,超出最大线程数限制,报错

1秒后,线程池中有空闲线程了,从阻塞队列中获取3、4并执行。
在这里插入图片描述

参考博客:
https://guisu.blog.csdn.net/article/details/7945539
https://blog.csdn.net/weixin_42934146/article/details/108661604

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

瑟 王

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值