线程池相关-shutdown、shutdownNow和awaitTermination

线程池相关,这里记录 shutdownshutdownNowawaitTermination 的功能用法。

shutdown

看看源码中对它的描述:

/**
 * Initiates an orderly shutdown in which previously submitted
 * tasks are executed, but no new tasks will be accepted.
 * Invocation has no additional effect if already shut down.
 *
 * 

This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that. * * @throws SecurityException {@inheritDoc} */

主要看第一段,大意是:

  • shutdown 调用之前的任务会被执行下去
  • 不会再接受新的任务
  • 如果已经 shutdown 了,再调用不会有其他影响

写代码实践一下,提交10个任务,在第6个任务的时候执行 shutdown

static void shutdownTest() {
    int count = 10;
    ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
    for (int i = 0; i < count; i++) {
        //try {
        tp.execute(new Task(i));
        //}

//            catch (RejectedExecutionException e) {
//                System.out.println("rejected, task-" + i);
//            }

        if (i == 5) {
            tp.shutdown();
        }
    }

    try {
        tp.awaitTermination(1, TimeUnit.DAYS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("-----------------");
    System.out.println("all tests finished");
}

static class Task implements Runnable {
    String name = "";

    public Task(int i) {
        name = "task-" + i;
    }

    public String getName() {
        return name;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(2000);
            System.out.println("sleep completed, " + getName());
        } catch (InterruptedException e) {
            System.out.println("interrupted, " + getName());
        }
        System.out.println(getName() + " finished");
    }
}

public static void main(String[] args) {
    shutdownTest();
}

运行结果如下,发现报出了 RejectedExecutionException,说明任务被拒绝了:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task a_playground.thread.ThreadPoolTest$Task@12a3a380 rejected from java.util.concurrent.ThreadPoolExecutor@29453f44[Shutting down, pool size = 1, active threads = 1, queued tasks = 5, completed tasks = 0]
    at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2047)
    at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:823)
    at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1369)
    at a_playground.thread.ThreadPoolTest.shutdownTest(ThreadPoolTest.java:18)
    at a_playground.thread.ThreadPoolTest.main(ThreadPoolTest.java:69)
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished

Process finished with exit code 1

把上面程序的注释去掉,catch 一下这个RejectedExecutionException,运行结果如下:

rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
-----------------
all tests finished

Process finished with exit code 0

程序运行到了最后,可以看到,第6个任务之后的任务都被拒绝了,其他任务正常执行。

所以 shutdown 方法将线程池状态置为 SHUTDOWN,线程池并不会立即停止,要等正在执行和队列里等待的任务执行完才会停止。

shutdownNow

看看源码中对它的描述:

/**
 * Attempts to stop all actively executing tasks, halts the
 * processing of waiting tasks, and returns a list of the tasks
 * that were awaiting execution. These tasks are drained (removed)
 * from the task queue upon return from this method.
 *
 * 

This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to * do that. * *

There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation * cancels tasks via {@link Thread#interrupt}, so any task that * fails to respond to interrupts may never terminate. * * @throws SecurityException {@inheritDoc} */

第一段大意如下:

  • 尝试停止所有正在执行的任务
  • 停止等待任务的处理,并返回等待任务的列表
  • 该方法返回时,这些等待的任务将从队列中清空

用代码实践一下,还是之前的代码,把 shutdown 改为 shutdownNow,并打印一下返回的等待任务:

static void shutdownNowTest() {
    int count = 10;
    ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
    for (int i = 0; i < count; i++) {
        try {
            tp.execute(new Task(i));
        } catch (RejectedExecutionException e) {
            System.out.println("rejected, task-" + i);
        }

        if (i == 5) {
            List tasks = tp.shutdownNow();
            for (Runnable task : tasks) {
                if (task instanceof Task) {
                    System.out.println("waiting task: " + ((Task) task).getName());
                }
            }
        }
    }

    try {
        tp.awaitTermination(1, TimeUnit.DAYS);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("-----------------");
    System.out.println("all tests finished");
}

运行结果如下:

interrupted, task-0
task-0 finished
waiting task: task-1
waiting task: task-2
waiting task: task-3
waiting task: task-4
waiting task: task-5
rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
-----------------
all tests finished

Process finished with exit code 0

可以看到调用 shutdownNow 后,第一个任务0正在睡眠的时候,触发了 interrupt 中断,之前等待的任务1-5被从队列中清除并返回,之后的任务被拒绝。

这时回到对 shutdownNow 描述的第三段:

* 

There are no guarantees beyond best-effort attempts to stop * processing actively executing tasks. This implementation * cancels tasks via {@link Thread#interrupt}, so any task that * fails to respond to interrupts may never terminate. *

大意是,该方法是通过 interrupt 方法去终止正在运行的任务的,因此无法响应 interrupt 中断的任务可能不会被终止。所以,该方法是无法保证一定能终止任务的。

所以 shutdownNow 方法将线程池状态置为 STOP,试图让线程池立刻停止,但不一定能保证立即停止,要等所有正在执行的任务(不能被 interrupt 中断的任务)执行完才能停止。

awaitTermination

shutdownshutdownNow 的方法描述的第二段分别是这样的:

shutdown:不会等已提交的任务(在等待队列中的任务)完成执行,让 awaitTermination来实现这个功能

* 

This method does not wait for previously submitted tasks to * complete execution. Use {@link #awaitTermination awaitTermination} * to do that.

shutdownNow:不会等已执行的任务的完成执行,让 awaitTermination来实现这个功能

* 

This method does not wait for actively executing tasks to * terminate. Use {@link #awaitTermination awaitTermination} to * do that.

awaitTermination 的功能如下:

  • 阻塞当前线程,等已提交和已执行的任务都执行完,解除阻塞
  • 当等待超过设置的时间,检查线程池是否停止,如果停止返回 true,否则返回 false,并解除阻塞

我们在上文中就使用了 awaitTermination :

try {
    tp.awaitTermination(1, TimeUnit.DAYS);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");

任务没执行完,且没到设置时间,是不会执行下面两行打印代码的,现在把等待时间设置为1秒:

try {
    boolean isStop = tp.awaitTermination(1, TimeUnit.SECONDS);
    System.out.println("is pool finished: " + isStop);
} catch (InterruptedException e) {
    e.printStackTrace();
}
System.out.println("-----------------");
System.out.println("all tests finished");

执行第一个 shutdown 的例子,结果如下:

rejected, task-6
rejected, task-7
rejected, task-8
rejected, task-9
is pool finished: false
-----------------
all tests finished
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished

Process finished with exit code 0

可以看到,达到设置时间后,就不再阻塞当前线程了,直接打印了下面两行代码,并且返回了 false 说明线程池没有停止。

有时我们需要主线程等所有子线程执行完毕后再运行,在所有任务提交后,调用shutdown触发 awaitTermination,阻塞主线程,当所有子线程执行完毕后,解除阻塞。

要注意的是,这个方法单独使用是无法停止线程池的,在如下代码中,注释了对 shutdown 的调用:

static void shutdownTest() {
    int count = 10;
    ThreadPoolExecutor tp = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new LinkedBlockingDeque<>(count));
    for (int i = 0; i < count; i++) {
        try {
            tp.execute(new Task(i));
        } catch (RejectedExecutionException e) {
            System.out.println("rejected, task-" + i);
        }

//            if (i == 5) {
//                tp.shutdown();
//            }
    }

    try {
        boolean isStop = tp.awaitTermination(1, TimeUnit.SECONDS);
        System.out.println("is pool finished: " + isStop);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("-----------------");
    System.out.println("all tests finished");
}

结果如下:

is pool finished: false
-----------------
all tests finished
sleep completed, task-0
task-0 finished
sleep completed, task-1
task-1 finished
sleep completed, task-2
task-2 finished
sleep completed, task-3
task-3 finished
sleep completed, task-4
task-4 finished
sleep completed, task-5
task-5 finished
sleep completed, task-6
task-6 finished
sleep completed, task-7
task-7 finished
sleep completed, task-8
task-8 finished
sleep completed, task-9
task-9 finished

任务都执行完了,但程序并没有终止,线程池没有被停止(没有出现 Process finished with exit code 0 的提示)。所以awaitTermination 要和 shutdown 结合使用。

需要注意的是,awaitTerminationshutdown 执行时都会申请锁,awaitTermination 需要在 shutdown 调用后调用,awaitTermination会在代码中不断检查线程池是否停止(这需要调用 shutdown后等任务全部执行完毕),如果停止,则返回true并释放锁。

awaitTermination代码:

public boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException {
    long nanos = unit.toNanos(timeout);
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (;;) {
            if (runStateAtLeast(ctl.get(), TERMINATED))
                return true;
            if (nanos <= 0)
                return false;
            nanos = termination.awaitNanos(nanos);
        }
    } finally {
        mainLock.unlock();
    }
}

如果 shutdownawaitTermination后调用的话,在awaitTermination 未超时前,它不会释放锁;而 shutdown 也无法得到锁去让线程池停止。这就形成了死锁。

你可能感兴趣的:(线程池相关-shutdown、shutdownNow和awaitTermination)