ThreadPoolExecutor类详解

JDK的ThreadPoolExecutor类实现了线程池,在它的构造函数中,有五个参数,让我们看看这五个参数具体作用是什么。

  • corePoolSize,核心线程池数量。
    默认情况下会一直存活,当请求进来时,如果当时的存活线程数小于核心线程数量,就会去新创建线程来处理这个请求,即使有其他线程是空闲的。在allowCoreThreadTimeout参数设置会true时,核心线程也会超时退出,默认是false超时不退出。
  • maxPoolSize,存活线程最大数量。
    当线程数大于或等于核心线程,且任务队列已满时,线程池会继续创建新的线程处理请求,直到maxPoolSize的限制数量。如果当前存活的线程已经到了maxPoolSize的限制数量,且任务队列已满,线程池会拒绝处理任务并抛出异常。
  • keepAliveTime,线程可空闲时间。
    当线程空闲时间打到keepAliveTime时,线程会退出,直到存活线程数量等于核心线程数量。如果allowCoreThreadTimeout参数设置会true时,核心线程也会退出,直到线程数为0。
  • TimeUnit,空闲线程的保留时间单位。
  • workQueue ,缓存工作队列,用来缓存等待执行的任务。
  • queueCapacity,任务队列容量。
  • handler:表示当拒绝处理任务时的策略。
    有四种取值:
    (1)ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常,默认此配置。
    (2)ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    (3)ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)。
    (4)ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务。

任务队列的选用
任务队列一般选用LinkedBlockingQueue和Synchronous。注意当使用LinkedBlockingQueue时,一定要设置队列的大小,否则会默认设置队列的大小为Integer.MaxValue,可能会造成资源耗尽。

线程池的关闭
ThreadPoolExecutor提供了两个方法去关闭线程池。
shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务。
shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务。

代码验证
我们来写一个测试类验证一下

     public static void main(String[] args) {
            int corePoolSize = 2;
            int maximumPoolSize = 5;
            int keepAliveTime = 10;
            BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);

            ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
                    TimeUnit.SECONDS, workQueue);
            try {
                test(threadPoolExecutor, 3);
            } catch (Exception e) {
                System.out.println(e.getMessage());
            } finally {
                //最后关闭线程池
                threadPoolExecutor.shutdown();
            }

        }

        public static void test(ThreadPoolExecutor threadPoolExecutor, int taskCount) {
            for (int i = 0; i < taskCount; i++) {
                threadPoolExecutor.submit(new TaskTest());
                System.out.println("总任务数:" + threadPoolExecutor.getTaskCount() + ",已完成任务数:" + threadPoolExecutor.getCompletedTaskCount()
                        + ",线程池线程数目:" + threadPoolExecutor.getPoolSize() + ",处于运行状态的数目:" + threadPoolExecutor.getActiveCount());
                System.out.println("缓存队列任务数:"+threadPoolExecutor.getQueue().size());
            }
        }

        static class TaskTest implements Runnable {

            @Override
            public void run() {
                try {
                    //为了更好的效果,休眠一秒
                    Thread.sleep(1000);
                    System.out.println(Thread.currentThread().getName() + "执行完毕");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

我们先把核心线程数设置成2,最大线程数设置成5,缓存工作队列设置成2,然后把我们要执行的任务书设置成3个,看下运行的结果是什么。

总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕
pool-1-thread-2执行完毕

从最后执行完毕时打印的线程名称可知,最多只有两个线程在执行任务(核心线程数为2),而当前的总任务数达到核心任务数后,新的任务会被放到缓存队列中,因为缓存队列还没满,所以线程池不会去创建新的线程去处理任务。那我们再来试一下缓存队列满了之后会怎么样,我们把任务数设置成5,结果如下。

总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
总任务数:5,已完成任务数:0,线程池线程数目:3,处于运行状态的数目:2
缓存队列任务数:2
pool-1-thread-1执行完毕
pool-1-thread-2执行完毕
pool-1-thread-3执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕

果然,第三个线程出来了,当缓存队列也满了的时候,线程池会创建新的线程去处理任务,当然这个线程数也是会被我们设置的maximumPoolSize最大线程数限制的,最大允许线程数为5个,再加上缓存队列的两个,所以最大可处理为7个任务,当第八个任务来了的时候会怎么样呢,我们来试试,把任务数提高到8个,结果如下。

总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
总任务数:5,已完成任务数:0,线程池线程数目:3,处于运行状态的数目:3
缓存队列任务数:2
总任务数:6,已完成任务数:0,线程池线程数目:4,处于运行状态的数目:4
缓存队列任务数:2
总任务数:7,已完成任务数:0,线程池线程数目:5,处于运行状态的数目:5
缓存队列任务数:2
Task java.util.concurrent.FutureTask@68de145 rejected from java.util.concurrent.ThreadPoolExecutor@27fa135a[Running, pool size = 5, active threads = 5, queued tasks = 2, completed tasks = 0]
pool-1-thread-1执行完毕
pool-1-thread-3执行完毕
pool-1-thread-5执行完毕
pool-1-thread-2执行完毕
pool-1-thread-4执行完毕
pool-1-thread-1执行完毕
pool-1-thread-3执行完毕

最终被处理的任务数为七个,而且抛出了异常,第八个任务被舍弃掉了(默认ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常)。

我们再来测试一下两种关闭线程池方法的区别,先来试试shoudownNow()方法,在原来的代码上改动一下。

test(threadPoolExecutor, 4);
List<Runnable> runnableList = threadPoolExecutor.shutdownNow();
System.out.println("未处理任务数:"+runnableList.size());

结果如下:

总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
未处理任务数:2
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕

关闭线程池后返回的未处理任务数为2,最终也只处理了前两条任务。我们再来试试shutdown()方法。

threadPoolExecutor.shutdown();

结果如下:

总任务数:1,已完成任务数:0,线程池线程数目:1,处于运行状态的数目:1
缓存队列任务数:0
总任务数:2,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:0
总任务数:3,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:1
总任务数:4,已完成任务数:0,线程池线程数目:2,处于运行状态的数目:2
缓存队列任务数:2
pool-1-thread-2执行完毕
pool-1-thread-2执行完毕
pool-1-thread-2执行完毕
pool-1-thread-1执行完毕

四个任务全部执行完成。

今天的总结就到这里,共勉。

猜你喜欢

转载自blog.csdn.net/peerless_fu/article/details/81676000
今日推荐