zoukankan      html  css  js  c++  java
  • 死磕 java线程系列之线程池深入解析——构造方法

    ThreadPoolExecutor

    (手机横屏看源码更方便)


    注:java源码分析部分如无特殊说明均基于 java8 版本。

    简介

    ThreadPoolExecutor的构造方法是创建线程池的入口,虽然比较简单,但是信息量很大,由此也能引发一系列的问题,同样地,这也是面试中经常被问到的问题,下面彤哥只是列举了一部分关于ThreadPoolExecutor构造方法的问题,如果你都能回答上来,则可以不用看下面的分析了。

    问题

    (1)ThreadPoolExecutor有几个构造方法?

    (2)ThreadPoolExecutor最长的构造方法有几个参数?

    (3)keepAliveTime是做什么用的?

    (7)核心线程会不会超时关闭?能不能超时关闭?

    (4)ConcurrentLinkedQueue能不能作为任务队列的参数?

    (5)默认的线程是怎么创建的?

    (6)如何实现自己的线程工厂?

    (7)拒绝策略有哪些?

    (8)默认的拒绝策略是什么?

    构造方法

    好了,我们直接上代码。

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
    
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
    

    ThreadPoolExecutor有四个构造方法,其中前三个最终都是调用最后一个,它有7个参数,分别为corePoolSize、maximumPoolSize、keepAliveTime、unit、workQueue、threadFactory、handler。

    corePoolSize

    核心线程数。

    当正在运行的线程数小于核心线程数时,来一个任务就创建一个核心线程;

    当正在运行的线程数大于或等于核心线程数时,任务来了先不创建线程而是丢到任务队列中。

    maximumPoolSize

    最大线程数。

    当任务队列满了时【本篇文章由公众号“彤哥读源码”原创】,来一个任务才创建一个非核心线程,但不能超过最大线程数。

    keepAliveTime + unit

    线程保持空闲时间及单位。

    默认情况下,此两参数仅当正在运行的线程数大于核心线程数时才有效,即只针对非核心线程。

    但是,如果allowCoreThreadTimeOut被设置成了true,针对核心线程也有效。

    即当任务队列为空时,线程保持多久才会销毁,内部主要是通过阻塞队列带超时的poll(timeout, unit)方法实现的。

    workQueue

    任务队列。

    当正在运行的线程数大于或等于核心线程数时,任务来了是先进入任务队列中的。

    这个队列必须是阻塞队列,所以像ConcurrentLinkedQueue就不能作为参数,因为它虽然是并发安全的队列,但是它不是阻塞队列。

    // ConcurrentLinkedQueue并没有实现BlockingQueue接口
    public class ConcurrentLinkedQueue<E> extends AbstractQueue<E>
            implements Queue<E>, java.io.Serializable {
        // ...【本篇文章由公众号“彤哥读源码”原创】
    }
    

    threadFactory

    线程工厂。

    默认使用的是Executors工具类中的DefaultThreadFactory类,这个类有个缺点,创建的线程的名称是自动生成的,无法自定义以区分不同的线程池,且它们都是非守护线程。

    static class DefaultThreadFactory implements ThreadFactory {
            private static final AtomicInteger poolNumber = new AtomicInteger(1);
            private final ThreadGroup group;
            private final AtomicInteger threadNumber = new AtomicInteger(1);
            private final String namePrefix;
    
            DefaultThreadFactory() {
                SecurityManager s = System.getSecurityManager();
                group = (s != null) ? s.getThreadGroup() :
                                      Thread.currentThread().getThreadGroup();
                namePrefix = "pool-" +
                              poolNumber.getAndIncrement() +
                             "-thread-";
            }
    
            public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                                      namePrefix + threadNumber.getAndIncrement(),
                                      0);
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
        }
    

    那怎么自定义一个线程工厂呢?

    其实也很简单,自己实现一个ThreadFactory,然后把名称和是否是守护进程当作构造方法的参数传进来就可以了。

    有兴趣的同学可以参考netty中的默认线程工厂或者google中的线程工厂。

    io.netty.util.concurrent.DefaultThreadFactory
    com.google.common.util.concurrent.ThreadFactoryBuilder
    

    handler

    拒绝策略。

    拒绝策略表示当任务队列满了且线程数也达到最大了,这时候再新加任务,线程池已经无法承受了,这些新来的任务应该按什么逻辑来处理。

    常用的拒绝策略有丢弃当前任务、丢弃最老的任务、抛出异常、调用者自己处理等待。

    默认的拒绝策略是抛出异常,即线程池无法承载了,调用者再往里面添加任务会抛出异常。

    默认的拒绝策略虽然比较简单粗暴,但是相对于丢弃任务策略明显要好很多,最起码调用者自己可以捕获这个异常再进行二次处理。

    彩蛋

    OK,ThreadPoolExecutor的构造方法这块我们今天进行了深入解析,关于这块,您还有什么问题呢?欢迎留言评论、私聊勾搭。


    欢迎关注我的公众号“彤哥读源码”,查看更多源码系列文章, 与彤哥一起畅游源码的海洋。

    qrcode

  • 相关阅读:
    二级指针内存模型(二)
    Winserver-FailoverCluster验证异常
    IIS-This configuration section cannot be used at this path.
    SQL SERVER-Extendevent捕获堵塞
    SQL SERVER-Extendevent
    Powershell-加域脚本
    SQL SERVER-端口Port
    WinServer-SMTP服务
    Linux-开机启动程序
    SQL SERVER-修改服务器名称
  • 原文地址:https://www.cnblogs.com/tong-yuan/p/11681782.html
Copyright © 2011-2022 走看看