【线程池】线程池的初始配置详解

邓敏 2021年12月15日 142次浏览

创建一个线程池

首先我们看一个创建线程池的例子

执行类

public class ThreadInit {

    /**
     * corePoolSize - 要保留在池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut
     * maximumPoolSize - maximumPoolSize的最大线程数
     * keepAliveTime - 当线程数大于核心数时,这是多余空闲线程在终止前等待新任务的最长时间。
     * unit - keepAliveTime参数的时间单位
     * workQueue - 用于在执行任务之前保存任务的队列。 这个队列将只保存execute方法提交的Runnable任务。
     * threadFactory - 执行程序创建新线程时使用的工厂
     * handler - 由于达到线程边界和队列容量而阻塞执行时使用的处理程序
     * @param args
     */
    public static void main(String[] args) {
        BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(10);
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1,5,10,TimeUnit.SECONDS,workQueue,new TestThreadFactory(),new MyRejectPolicy());

        for (int i = 0; i < 20; i++) {
            threadPoolExecutor.execute(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":执行了");
            });
        }
        threadPoolExecutor.shutdown();
    }
}

拒绝策略类

public class MyRejectPolicy implements RejectedExecutionHandler {
    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.println("被拒绝了");
    }
}

线程工程类

public class TestThreadFactory implements ThreadFactory {

    private static AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public Thread newThread(Runnable r) {
        atomicInteger.addAndGet(1);
        return new Thread(r,"自定义名字-"+atomicInteger.get());
    }
}

参数讲解

仔细看完上面这个创建线程池的例子后,就会发现,创建一个线程池,有六个可以配置的参数,这六个参数分别是image.png
首先我们先按顺序来讲解一下这六个参数的各个含义

corePoolSize

核心线程数是线程池会一直保持活跃的线程数,比如我们核心线程数设置为5个线程,那么当线程任务进入到线程池中会始终保持有五个线程的空位使用。

maximumPoolSize

最大线程数与核心线程数还不太一样,最大线程数是线程池再判断线程队列是否满了,如果满了则会在核心线程数的基础上去创建新的线程,直到线程数达到了最大线程数之后变不会在创建。
这里有很多人都会有一个误区,把核心线程数理解为最小活跃线程,以为当进入到线程池后,如果超过核心线程数的任务会直接去创建线程执行,直到线程数满了之后然后在把线程存放到线程池的队列中。其实这种理解是错误的,在Java的线程池中,线程池会先去判断线程队列有没有满,如果满了,才会在核心线程数上去创建线程。
例如我们上面那个创建线程的例子来看,核心线程数为1个,最大线程数为5个,队列最大可以存放10个。当客户端开始执行20个线程的同时,核心线程会占去一个,之后的线程会先存放到线程池的队列中,此时在队列中存放了10个后发现还有9个线程需要被放入到线程池中,这是线程池就会把核心线程数扩大到最大线程数5个。
可以参考下这个张图片
image.png

keepAliveTime

空闲线程的存活时间代表着,如果当我们例子中的线程池在线程数到达最大值的情况下,线程池里面的任务都执行完了,这个时候除了1个核心线程,剩下的4个线程就会保持我们设置的keepAliveTime时间来等待,如果设置10秒后发现还是没有线程进来,这个时候这4个空闲的线程就会慢慢的关闭掉,直到下次再有线程过来,等到线程队列满了之后才会去创建这些线程。

workQueue

workQueue就是我们的线程队列,例如我们现在设置的队列BlockingQueue,他是属于阻塞队列,并且有最大值。这个设置需要看具体使用场景来设置,例如我们不想丢弃任何一个线程策略,就可以设置Queue,这时他就可以没有限制的去存放你的线程任务。

threadFactory

线程工厂就比较有意思了,他可以做很多的时候,例如上面的例子中,线程工厂做了一个线程名称的自定义,因为在业务场景中,线程池中自带默认的创建线程工厂可能不太符合我们业务需求,例如自定义线程名字,线程计数等等。这种自定义的东西就可以以自己需求来判断如何改造自己线程创建的方式。

handler

这个的作用是基于线程被拒绝后使用的,比如线程被拒绝后,我们想要知道被拒绝的线程有多少个,或者线程被拒绝后如何去打印被拒绝线程的日志。

总结

通过上面的参数详解后,我们可以得出线程池的几个特点和注意事项

  • 线程池希望保持较少的线程数,只有在负载变大的时候才会去扩线程。
  • 线程池只有在任务队列填满时才创建多于 corePoolSize 的线程,如果使用的是无界队列(例如 LinkedBlockingQueue),那么由于队列不会满,所以线程数不会超过 corePoolSize。
  • 通过设置 corePoolSize 和 maximumPoolSize 为相同的值,就可以创建固定大小的线程池。
  • 通过设置 maximumPoolSize 为很高的值,例如 Integer.MAX_VALUE,就可以允许线程池创建任意多的线程。