线程池相关-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.
 *
 * <p>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.
 *
 * <p>This method does not wait for actively executing tasks to
 * terminate.  Use {@link #awaitTermination awaitTermination} to
 * do that.
 *
 * <p>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<Runnable> 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 描述的第三段:

* <p>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来实现这个功能

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

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

* <p>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 也无法得到锁去让线程池停止。这就形成了死锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,463评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,868评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,213评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,666评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,759评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,725评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,716评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,484评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,928评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,233评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,393评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,073评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,718评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,308评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,538评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,338评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,260评论 2 352

推荐阅读更多精彩内容