验证线程池线程分配

38 阅读3分钟

为了减少每次资源的消耗,提高对资源的利用率,引入池化技术。常见的池化技术有HTTP、数据库、Netty连接池,Redis客户端连接池,以及我们本章要说的是线程池。

线程池作用

线程池可以帮助我们限制和管理线程资源。

  1. 降低资源消耗

    降低创建线程和消耗线程的损耗

  2. 提高响应速度

    任务到达不需要等待系统创建,可以立即执行

  3. 提高线程可管理性

    进行统一分配、调优和监控

验证线程池核心线程与最大线程关系

geektime的Java性能调优实战中的图示,线程池中最重要的三个参数为:corePoolSize核心线程,maximumPoolSize最大线程数,workQueue任务队列。

image.png

1. 创建线程池

我们可以看到ThreadPoolExecutor的构造函数的参数分别为corePoolSize核心数,maximumPoolSize最大线程数,keepAliveTime存活时间,unit存活时间单位,workQueue任务队列,threadFactory线程工厂,handler拒绝策略。

  • keepAliveTime

线程池中的线程数量大于 corePoolSize 的时候,如果这时没有新的任务提交,核心线程外的线程不会立即销毁,而是会等待,直到等待的时间超过了 keepAliveTime才会被回收销毁

  • handler

    • ThreadPoolExecutor.AbortPolicy:抛出 RejectedExecutionException来拒绝新任务的处理。

    • ThreadPoolExecutor.CallerRunsPolicy:调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,可以选择这个策略。

    • ThreadPoolExecutor.DiscardPolicy:不处理新任务,直接丢弃掉。

    • ThreadPoolExecutor.DiscardOldestPolicy:此策略将丢弃最早的未处理的任务请求。

public static void main(String[] args) {
    // public ThreadPoolExecutor(int corePoolSize,
    //                              int maximumPoolSize,
    //                              long keepAliveTime,
    //                              TimeUnit unit,
    //                              BlockingQueue<Runnable> workQueue,
    //                              ThreadFactory threadFactory,
    //                              RejectedExecutionHandler handler拒绝策略。) {
    ExecutorService threadPoolExecutor =
            new ThreadPoolExecutor(3,5,
                    1L, TimeUnit.SECONDS,
                    new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(),
                    new ThreadPoolExecutor.AbortPolicy());
  1. 验证线程池线程分配情况

当线程小于等于核心线程数时,查看输出我们可以看到分别运行在三个线程中

    for(int i = 0;i < 3;i++){
        threadPoolExecutor.execute(()->{
            System.out.println(Thread.currentThread().getName() + " ===> 运行");
        });
    }
    threadPoolExecutor.shutdown();
}
  • pool-1-thread-1 ===> 运行
  • pool-1-thread-3 ===> 运行
  • pool-1-thread-2 ===> 运行

当线程大于核心线程数,但仍在工作队列可容纳数量内时,线程1、2、3得到了复用。

for(int i = 0;i < 6;i++){
    threadPoolExecutor.execute(()->{
        System.out.println(Thread.currentThread().getName() + " ===> 运行");
    });
}
threadPoolExecutor.shutdown();
  • pool-1-thread-1 ===> 运行
  • pool-1-thread-3 ===> 运行
  • pool-1-thread-2 ===> 运行
  • pool-1-thread-3 ===> 运行
  • pool-1-thread-1 ===> 运行
  • pool-1-thread-2 ===> 运行

当线程大于工作队列可容纳数量,小于等于最大线程数时,我们可以看到线程4被创建了。

for(int i = 0;i < 7;i++){
    threadPoolExecutor.execute(()->{
        System.out.println(Thread.currentThread().getName() + " ===> 运行");
    });
}
  • pool-1-thread-1 ===> 运行
  • pool-1-thread-3 ===> 运行
  • pool-1-thread-1 ===> 运行
  • pool-1-thread-2 ===> 运行
  • pool-1-thread-3 ===> 运行
  • pool-1-thread-4 ===> 运行
  • pool-1-thread-2 ===> 运行

超过最大线程数时,会根据拒绝策略进行操作,看到输出抛出异常。

for(int i = 0;i < 10;i++){
    threadPoolExecutor.execute(()->{
        System.out.println(Thread.currentThread().getName() + " ===> 运行");
    });
}
Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task threadPooltest.ThreadPoolMainTest$$Lambda$1/2093176254@7530d0a rejected from java.util.concurrent.ThreadPoolExecutor@27bc2616[Running, pool size = 5, active threads = 0, queued tasks = 0, completed tasks = 8]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
	at threadPooltest.ThreadPoolMainTest.main
  1. 线程分配流程图

image.png