Java线程池

1. 线程池的作用

  1. 避免创建线程 : 避免每次使用线程时 , 都需要 创建线程对象 ;
  2. 统一管理 : 统一管理线程 , 重用存在的线程 , 减少线程对象创建 , 销毁的开销 ;
  3. 控制并发 : 可 控制线程的最大并发数 , 提高资源使用效率 , 避免资源竞争导致堵塞

2. 线程池的关键参数

首先看创建线程池的方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) 

关键参数:

  1. 核心线程数corePoolSize,也就是即使线程数小于核心线程数也不会回收。除非设置了allowCoreThreadTimeOut
  2. 最大线程数maximumPoolSize,也就是可以容纳的最大线程数量包括核心线程和非核心线程。非核心线程空闲的时候会被回收。
  3. 最大存活时间keepAliveTime,非核心线程超过该时间是闲置的就回收该线程。
  4. 时间单位
  5. 任务队列workQueue, 线程池中维护了一个任务队列 , 线程池启动后 , 会不停的从任务队列中取出任务 , 如果有新任务 , 执行如下操作 ;
    如果线程数小于核心线程数 ( CoreSize ) , 那么创建核心线程 , 执行上述任务 ;
    如果 线程数 大于核心线程数 ( CoreSize ) , 小于最大线程数 ( MaxSize ) , 那么创建非核心线程 , 执行上述任务 ;
    如果 线程数 超过 最大线程数 ( MaxSize )
    如果 任务队列没满 , 则将任务放入任务队列 ;
    如果 任务队列满了 , 则抛出异常 ; 这里一般情况下需要手动处理这种情况 , 任务拒绝后 , 处理善后
  6. 线程工厂,控制如何产生线程。
  7. 拒绝策略RejectedExecutionHandler,若线程池中的核心线程数被用完且阻塞队列已排满,则此时线程池的资源已耗尽,线程池将没有足够的线程资源执行新的任务。为了保证操作系统的安全,线程池将通过拒绝策略处理新添加的线程任务。

2.1 核心线程数

CPU密集型 core + 1, IO密集型: 2 * core
CPU核心的获取方法Runtime.getRuntime().availableProcessors();

2.2 最大线程数

根据任务数量和任务时间确定:maxCount = tasks/(1/taskcost) = tasks*taskcout

2.3 任务队列

如果运行的线程少于corePoolSize,则 Executor始终首选添加新的线程,而不进行排队。(如果当前运行的线程小于corePoolSize,则任务根本不会存入queue中,而是直接运行)如果运行的线程大于等于 corePoolSize,则 Executor始终首选将请求加入队列,而不添加新的线程。如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。主要有3种类型的BlockingQueue:

2.3.1 无界队列

队列大小无限制,常用的为无界的LinkedBlockingQueue,如果任务耗时很大,可能会导致大量新任务在队列中堆积最终导致OOM。

2.3.2 有界队列

遵循FIFO的队列ArrayBlockingQueue,另外一类是优先级队列PriorityBlockingQueuepriorityBlockingQueue中的优先级由任务的Comparator决定。使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

2.3.3 同步移交队列

使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列。使用该队列时,如果任务数大于最大线程数,会抛出异常。同步移交,任务队列没有大小。

2.4 拒绝策略

当任务队列已经满了依旧提交任务,采取的策略,JDK有四种

2.4.1 AbortPolicy中止策略

默认策略,使用该策略时在饱和时会抛出RejectedExecutionException(继承自RuntimeException),调用者可捕获该异常自行处理。

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }

2.4.2 DiscardPolicy抛弃策略

不做任何处理直接抛弃。

    /**
     * A handler for rejected tasks that silently discards the
     * rejected task.
     */
    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

2.4.3 DiscardOldestPolicy抛弃旧任务策略

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

先将阻塞队列中的头元素出队抛弃,再尝试提交任务。如果此时阻塞队列使用PriorityBlockingQueue优先级队列,将会导致优先级最高的任务被抛弃,因此不建议将该种策略配合优先级队列使用。

2.4.4 CallerRunsPolicy调用者运行

    /**
     * A handler for rejected tasks that runs the rejected task
     * directly in the calling thread of the {@code execute} method,
     * unless the executor has been shut down, in which case the task
     * is discarded.
     */
    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

既不抛弃任务也不抛出异常,直接运行任务的run方法,换言之将任务回退给调用者来直接运行。使用该策略时线程池饱和后将由调用线程池的主线程自己来执行任务,因此在执行任务的这段时间里主线程无法再提交新任务,从而使线程池中工作线程有时间将正在处理的任务处理完成。

3. java通过的默认的四种线程池

jdk提供了四种线程池使用,其实是一种封装而已。分别是:

  1. Executors.newCachedThreadPool() 提供了无界线程池,可以进行自动线程回收
  2. Executors.newFixedThreadPool(int) 提供了固定大小线程池,内部使用无界队列
  3. Executors.newSingleThreadExecutor() 提供了单个后台线程。
  4. Executors.newScheduledThreadPool(int)创建一个定长线程池,支持定时及周期性任务执行。

3.1 newCachedThreadPool()

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

可以看到,该种方式创建,没有核心线程。最大线程数为最大Int数。并且没有任务队列。有任务就新建线程。

public class TestThreadPool {

    public static void main(String[] arg) {
       new TestThreadPool().testCacheThreadPool();
    }

    private void testCacheThreadPool() {
        ExecutorService cacheThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            int finalI = i;

            cacheThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + " " + finalI);
                }
            });
        }
    }
}
pool-1-thread-4 3
pool-1-thread-6 5
pool-1-thread-5 4
pool-1-thread-10 9
pool-1-thread-9 8
pool-1-thread-1 0
pool-1-thread-8 7
pool-1-thread-2 1
pool-1-thread-3 2
pool-1-thread-7 6

另外线程是可以回收的,测试如下,每添加一个任务,主线程休眠1s.

public class TestThreadPool {

    public static void main(String[] arg) {
       new TestThreadPool().testCacheThreadPool();
    }

    private void testCacheThreadPool() {
        ExecutorService cacheThreadPool = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) {
            int finalI = i;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            cacheThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + " " + finalI);
                }
            });
        }
    }
    
pool-1-thread-1 0
pool-1-thread-1 1
pool-1-thread-1 2
pool-1-thread-1 3
pool-1-thread-1 4
pool-1-thread-1 5
pool-1-thread-1 6
pool-1-thread-1 7
pool-1-thread-1 8
pool-1-thread-1 9

3.2 newFixedThreadPool(int)\

    public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

可见这种线程池都是核心线程,选用的是无界队列。

    private void testFixedThreadPool() {
        ExecutorService fixThread = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            fixThread.execute(new Runnable() {
                @Override
                public void run() {
                    //每次只能三个一起执行
                    System.out.println(Thread.currentThread().getName() + " " + finalI);
                }
            });
        }
    }
pool-1-thread-3 2
pool-1-thread-1 0
pool-1-thread-3 3
pool-1-thread-2 1
pool-1-thread-2 6
pool-1-thread-3 5
pool-1-thread-3 8
pool-1-thread-1 4
pool-1-thread-3 9
pool-1-thread-2 7

更加直观的看到,每个线程休眠1s,那么三个线程会同时执行。等待后再一起执行

    private void testFixedThreadPool() {
        ExecutorService fixThread = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            fixThread.execute(new Runnable() {
                @Override
                public void run() {
                    //每次只能三个一起执行
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(Thread.currentThread().getName() + " " + finalI);
                }
            });
        }
    }
pool-1-thread-3 2
pool-1-thread-1 0
pool-1-thread-2 1
pool-1-thread-2 5
pool-1-thread-3 3
pool-1-thread-1 4
pool-1-thread-2 6
pool-1-thread-1 8
pool-1-thread-3 7
pool-1-thread-2 9

3.3 newSingleThreadExecutor

一个核心线程

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

3.4

ScheduledThreadPoolExecutor它与ThreadPoolExecutor最大的不同就是可以设置延迟任务和周期任务

    /**
     * Creates a new {@code ScheduledThreadPoolExecutor} with the
     * given core pool size.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @throws IllegalArgumentException if {@code corePoolSize < 0}
     */
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE,
              DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
              new DelayedWorkQueue());
    }
  1. 使用多线程执行任务,不用担心任务执行时间过长而导致任务相互阻塞的情况,Timer是单线程执行的,因而会出现这个问题;
  2. 不用担心任务执行过程中,如果线程失活,其会新建线程执行任务,Timer类的单线程挂掉之后是不会重新创建线程执行后续任务的。
  3. ScheduledThreadPoolExecutor还提供了非常灵活的API,用于执行任务。其任务的执行策略主要分为两大类:①在一定延迟之后只执行一次某个任务;②在一定延迟之后周期性的执行某个任务。如下是其主要API:
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay, long period, TimeUnit unit);

第一个和第二个方法属于第一类,即在delay指定的延迟之后执行第一个参数所指定的任务,区别在于,第二个方法执行之后会有返回值,而第一个方法执行之后是没有返回值的。第三个和第四个方法则属于第二类,即在第二个参数(initialDelay)指定的时间之后开始周期性的执行任务,执行周期间隔为第三个参数指定的时间,但是这两个方法的区别在于第三个方法执行任务的间隔是固定的,无论上一个任务是否执行完成,而第四个方法的执行时间间隔是不固定的,其会在周期任务的上一个任务执行完成之后才开始计时,并在指定时间间隔之后才开始执行任务。

3.4.1 scheduleAtFixedRate

每间隔一段时间执行,分为两种情况:

  1. 当前任务执行时间小于间隔时间,每次到点即执行;
  2. 当前任务执行时间大于等于间隔时间,任务执行结束后立即执行下一次任务。相当于连续执行了。

线程执行时间为4s. period = 5s.当上一个任务执行完成,需要到达时间点才能执行。

public void testscheduleAtFixedRate() {
        System.out.println(toData() + " start task!!!");
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                System.out.println("Start: scheduleWithFixedDelay: " + Thread.currentThread().getName() + " "+new Date());
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End  : scheduleWithFixedDelay: " + Thread.currentThread().getName() + " " + new Date());
            }
        }, 0, 5 , SECONDS);
    }

    private String toData() {
        Long timeStamp = System.currentTimeMillis();  //获取当前时间戳
        SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String sd = sdf.format(new Date(Long.parseLong(String.valueOf(timeStamp))));      // 时间戳转换成时间
        return sd;
    }
2023-06-08 11:21:26 start task!!!
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:21:26 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:21:30 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:21:31 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:21:35 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:36 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:40 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:41 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:45 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:46 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:50 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:51 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:55 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:21:56 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:00 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:01 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:05 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:06 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:10 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:11 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:22:15 CST 2023

如果将执行时间设置为6s执行结果如下,当上一个任务结束就立即执行,因为上一个任务的执行时间已经超过了period.

2023-06-08 11:24:34 start task!!!
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:24:34 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:24:40 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:24:40 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:24:46 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:24:46 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:24:52 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:24:52 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:24:58 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:24:58 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:04 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:04 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:10 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:10 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:16 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:16 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:22 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:22 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:28 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:28 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:25:34 CST 2023

3.4.2 scheduleWithFixedDelay

每当上次任务执行完毕后,间隔一段时间执行。不管当前任务执行时间大于、等于还是小于间隔时间,执行效果都是一样的。都是当前任务执行完间隔一段时间。

    public void testScheduleWithFixedDelay() {
        System.out.println(toData() + " start task!!!");
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
        executorService.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                System.out.println("Start: scheduleWithFixedDelay: " + Thread.currentThread().getName() + " "+new Date());
                try {
                    Thread.sleep(6000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("End  : scheduleWithFixedDelay: " + Thread.currentThread().getName() + " " + new Date());
            }
        }, 0, 5 , SECONDS);
    }
2023-06-08 11:28:28 start task!!!
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:28:28 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:28:34 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:28:39 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-1 Thu Jun 08 11:28:45 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:28:50 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:28:56 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:01 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:07 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:12 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:18 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:23 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:29 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:34 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:40 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:45 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:51 CST 2023
Start: scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:29:56 CST 2023
End  : scheduleWithFixedDelay: pool-1-thread-2 Thu Jun 08 11:30:02 CST 2023

可以看到,执行结束后间隔5s启动下一个任务。

5. 如何正确关闭线程池

主要有五个方法:

void shutdown();
boolean isShutdown();
boolean isTerminated();
boolean awaitTerminated(long timeout,TimeUnit unit) throws InterruptedException;
List<Runnable> shutdownNow();

5.1 shutdown

shutdown()可以安全地关闭一个线程池,调用 shutdown() 方法之后线程池并不是立刻就被关闭,因为这时线程池中可能还有很多任务正在被执行,或是任务队列中有大量正在等待被执行的任务,调用 shutdown() 方法后线程池会在执行完正在执行的任务和队列中等待的任务后才彻底关闭。但这并不代表shutdown()操作是没有任何效果的,调用shutdown()方法后如果还有新的任务被提交,线程池则会根据拒绝策略直接拒绝后续新提交的任务。

private void testShutdown() {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        // 提交10个任务
        for (int i = 0; i< 10; i++) {
            executorService.execute(() -> {
                try {
                    Thread.sleep(500);
                    System.out.println(toData() + "  " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

		System.out.println("result: isShutDown = "+ executorService.isShutdown()+", before invoke shutdown()");

        executorService.shutdown();

        System.out.println("result: isShutDown = "+ executorService.isShutdown()+", after invoke shutdown()");
        System.out.println("submit next");
        
        try {
            // 继续提交任务
            executorService.execute(() -> {
                try {
                    Thread.sleep(500);
                    System.out.println(toData() + "  " + Thread.currentThread().getName());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        } catch (RejectedExecutionException exception) {
            System.out.println("Throw Exception " + exception.getMessage());
        }

    }
  
2023-06-08 14:51:23  pool-1-thread-2
2023-06-08 14:51:23  pool-1-thread-3
2023-06-08 14:51:23  pool-1-thread-1
result: isShutDown = false, before invoke shutdown()
result: isShutDown = true, after invoke shutdown()
submit next
Throw Exception Task TestThreadPool$$Lambda$48/0x0000000800c03410@7cd84586 rejected from java.util.concurrent.ThreadPoolExecutor@30dae81[Shutting down, pool size = 3, active threads = 3, queued tasks = 4, completed tasks = 3]
2023-06-08 14:51:23  pool-1-thread-2
2023-06-08 14:51:23  pool-1-thread-3
2023-06-08 14:51:23  pool-1-thread-1
2023-06-08 14:51:24  pool-1-thread-2
2023-06-08 14:51:24  pool-1-thread-1
2023-06-08 14:51:24  pool-1-thread-3
2023-06-08 14:51:24  pool-1-thread-2

首先提交10个任务,然后调用shutdown,然后继续提交任务可以看到,之前提交的任务依旧会执行,再次提交任务会抛出异常。

5.2 isShutdown

isShutdown(),它可以返回 true 或者 false 来判断线程池是否已经开始了关闭工作,也就是是否执行了 shutdown 或者 shutdownNow 方法。这里需要注意,如果调用isShutdown()方法的返回的结果为 true 并不代表线程池此时已经彻底关闭了,这仅仅代表线程池开始了关闭的流程,也就是说,此时可能线程池中依然有线程在执行任务,队列里也可能有等待被执行的任务。

5.3 isTerminated()

isTerminated()这个方法可以检测线程池是否真正“终结”了,这不仅代表线程池已关闭,同时代表线程池中的所有任务都已经都执行完毕了,因为我们刚才说过,调用shutdown方法之后,线程池会继续执行里面未完成的任务,不仅包括线程正在执行的任务,还包括正在任务队列中等待的任务。比如此时已经调用了shutdown方法,但是有一个线程依然在执行任务,那么此时调用 isShutdown 方法返回的是true,而调用 isTerminated 方法返回的便是false,因为线程池中还有任务正在在被执行,线程池并没有真正“终结”。直到所有任务都执行完毕了,调用 isTerminated() 方法才会返回 true,这表示线程池已关闭并且线程池内部是空的,所有剩余的任务都执行完毕了。

5.4 awaitTermination()

awaitTermination(),它本身并不是用来关闭线程池的,而是主要用来判断线程池状态的。比如我们给awaitTermination方法传入的参数是 10 秒,那么它就会陷入 10 秒钟的等待,直到发生以下三种情况之一:

  1. 等待期间(包括进入等待状态之前)线程池已关闭并且所有已提交的任务(包括正在执行的和队列中等待的)都执行完毕,相当于线程池已经“终结”了,方法便会返回 true;
  2. 等待超时时间到后,第一种线程池“终结”的情况始终未发生,方法返回 false;
  3. 等待期间线程被中断,方法会抛出 InterruptedException 异常。
    也就是说,调用 awaitTermination 方法后当前线程会尝试等待一段指定的时间,如果在等待时间内,线程池已关闭并且内部的任务都执行完毕了,也就是说线程池真正“终结”了,那么方法就返回 true,否则超时返回 fasle。我们则可以根据 awaitTermination() 返回的布尔值来判断下一步应该执行的操作。这个 10 秒钟期间,这个线程是会阻塞在这里的,就是说他一直是停在这个方法中的,直到 10 秒钟之后再往下执行,而且它会返回一个布尔值,然后我们把这个布尔值给打出来。

5.5 shutdownNow()

shutdownNow()是 5 种方法里功能最强大的,它与第一种 shutdown 方法不同之处在于名字中多了一个单词 Now,也就是表示立刻关闭的意思。在执行 shutdownNow 方法之后,首先会给所有线程池中的线程发送 interrupt 中断信号,尝试中断这些任务的执行,然后会将任务队列中正在等待的所有任务转移到一个List中并返回,我们可以根据返回的任务List来进行一些补救的操作,例如记录在案并在后期重试。

你可能感兴趣的:(java,开发语言)