一、概述

Java提供的接口 java.util.concurrent.ExecutorService是一种异步执行的机制,可以让任务在后台执行。其实例就像一个线程池,可以对任务进行统一的管理。

二、研究

Java提供的对ExecutorService的关闭方式有两种,一种是调用其shutdown()方法,另一种是调用shutdownNow()方法。这两者是有区别的。

以下内容摘自源代码内的注释

// 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 awaitTermination to do that.
// 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.
This method does not wait for actively executing tasks to terminate.  Use awaitTermination to do that.
There are no guarantees beyond best-effort attempts to stop processing actively executing tasks.  For example, typical implementations will cancel via interrupt, so any task that fails to respond to interrupts may never terminate.

两大段英文是什么意思呢?我来简单总结一下。

shutdown:
1、调用之后不允许继续往线程池内继续添加线程;
2、线程池的状态变为SHUTDOWN状态;
3、所有在调用shutdown()方法之前提交到ExecutorSrvice的任务都会执行;
4、一旦所有线程结束执行当前任务,ExecutorService才会真正关闭。

shutdownNow():
1、该方法返回尚未执行的 task 的 List;
2、线程池的状态变为STOP状态;
3、阻止所有正在等待启动的任务, 并且停止当前正在执行的任务。

简单点来说,就是:
shutdown()调用后,不可以再 submit 新的 task,已经 submit 的将继续执行
shutdownNow()调用后,试图停止当前正在执行的 task,并返回尚未执行的 task 的 list

三、源码分析

这里用的是JDK1.8,首先进入ThreadPoolExecutorshutDown()方法:

public void shutdown() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(SHUTDOWN);
        interruptIdleWorkers();
        onShutdown(); // hook for ScheduledThreadPoolExecutor
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
}

private void checkShutdownAccess() {
   SecurityManager security = System.getSecurityManager();
   if (security != null) {
       security.checkPermission(shutdownPerm);
       final ReentrantLock mainLock = this.mainLock;
       mainLock.lock();
       try {
           for (Worker w : workers)
               security.checkAccess(w.thread);
       } finally {
           mainLock.unlock();
       }
   }
}

private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

private void interruptIdleWorkers() {
    interruptIdleWorkers(false);
}

void onShutdown() {}

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

进入shutDownNow()方法看看:

public List<Runnable> shutdownNow() {
   List<Runnable> tasks;
   final ReentrantLock mainLock = this.mainLock;
   mainLock.lock();
   try {
       checkShutdownAccess();
       advanceRunState(STOP);
       interruptWorkers();
       tasks = drainQueue();
   } finally {
       mainLock.unlock();
   }
   tryTerminate();
   return tasks;
}

private void checkShutdownAccess() {
   SecurityManager security = System.getSecurityManager();
    if (security != null) {
        security.checkPermission(shutdownPerm);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers)
                security.checkAccess(w.thread);
        } finally {
            mainLock.unlock();
        }
    }
}

private void advanceRunState(int targetState) {
    for (;;) {
        int c = ctl.get();
        if (runStateAtLeast(c, targetState) ||
            ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))
            break;
    }
}

private void interruptWorkers() {
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        for (Worker w : workers)
            w.interruptIfStarted();
    } finally {
        mainLock.unlock();
    }
}

private List<Runnable> drainQueue() {
    BlockingQueue<Runnable> q = workQueue;
    ArrayList<Runnable> taskList = new ArrayList<Runnable>();
    q.drainTo(taskList);
    if (!q.isEmpty()) {
        for (Runnable r : q.toArray(new Runnable[0])) {
            if (q.remove(r))
                taskList.add(r);
        }
    }
    return taskList;
}

final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        if (workerCountOf(c) != 0) { // Eligible to terminate
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    terminated();
                } finally {
                    ctl.set(ctlOf(TERMINATED, 0));
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
        // else retry on failed CAS
    }
}

四、实战

1、Demo1

package com.concurrent.executorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author riemann
 * @date 2019/07/28 23:41
 */
public class ExecutorServiceDemo1 {

    static Runnable run = () -> {
        try {
            Thread.sleep(5000);
            System.out.println("thread finish");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(run);
        service.shutdown();
        service.execute(run);
    }

}

输出结果:

Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task com.concurrent.executorService.ExecutorServiceDemo1$$Lambda$1/1854731462@312b1dae rejected from java.util.concurrent.ThreadPoolExecutor@7530d0a[Shutting down, pool size = 1, active threads = 1, queued tasks = 0, 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 com.concurrent.executorService.ExecutorServiceDemo1.main(ExecutorServiceDemo1.java:25)
thread finish

当调用shutdown()之后,将不能继续添加任务,否则会抛出异常RejectedExecutionException。并且当正在执行的任务结束之后才会真正结束线程池。

2、Demo2

package com.concurrent.executorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author riemann
 * @date 2019/07/29 0:03
 */
public class ExecutorServiceDemo2 {

    static Runnable run = () -> {
        try {
            Thread.sleep(5000);
            System.out.println("thread finish");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    };

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(2);
        service.execute(run);
        service.shutdownNow();
    }

}

输出结果:

java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.concurrent.executorService.ExecutorServiceDemo2.lambda$static$0(ExecutorServiceDemo2.java:14)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
	at java.lang.Thread.run(Thread.java:745)

使用shutdownNow(),若线程中有执行sleep/wait/定时锁等,直接终止正在运行的线程并抛出 interrupt 异常。因为其内部是通过Thread.interrupt()实现的。
但是这种方法有很强的局限性。因为如果线程中没有执行sleep等方法的话,其无法终止线程。

3、Demo3

package com.concurrent.executorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author riemann
 * @date 2019/07/29 0:05
 */
public class ExecutorServiceDemo3 {

    static Runnable run = () -> {
        long num = 0;
        boolean flag = true;
        while (flag) {
            num += 1;
            if (num == Long.MAX_VALUE) {
                flag = false;
            }
        }
    };

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(1);
        service.execute(run);
        service.shutdownNow();
    }

}

很多代码中都会有这样的情况,比方说使用循环标记flag循环执行一些耗时长的计算任务, 直到满足某个条件之后才设置循环标记为false
如 Demo3 代码所示 (循环等待的情况),shutdownNow()无法终止线程。
如果遇到这种情况,可以使用如 Demo4 中的方法。

4、Demo4

package com.concurrent.executorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @author riemann
 * @date 2019/07/29 0:12
 */
public class ExecutorServiceDemo4 {

    static Runnable run = () -> {
        long num = 0;
        while (true && !Thread.currentThread().isInterrupted()) {
            num += 1;
        }
        System.out.println(num);
    };

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(1);
        service.execute(run);
        service.shutdownNow();
    }

}

输出结果:

0

对于循环等待的情况,可以引入变量Thread.currentThread().isInterrupted()来作为其中的一个判断条件。
isInterrupted()方法返回当前线程是否有被 interrupt。
shutdownNow()的内部实现实际上就是通过 interrupt 来终止线程,所以当调用shutdownNow()时,isInterrupted()会返回true
此时就可以跳出循环等待。
然而这也不是最优雅的解决方式,具体可以参见 Demo5。

5、Demo5

package com.concurrent.executorService;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author riemann
 * @date 2019/07/29 0:17
 */
public class ExecutorServiceDemo5 {

    static Runnable run = () -> {
        long num = 0;
        boolean flag = true;
        while (flag && !Thread.currentThread().isInterrupted()) {
            num += 1;
            if (num == Long.MAX_VALUE) {
                flag = false;
            }
        }
        System.out.println(num);
    };

    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(1);
        service.execute(run);
        service.shutdown();
        try {
            if (!service.awaitTermination(2, TimeUnit.SECONDS)) {
                service.shutdownNow();
            }
        } catch (InterruptedException e) {
            service.shutdownNow();
        }
    }

}

输出结果:

999032162

这里。先调用shutdown()使线程池状态改变为SHUTDOWN,线程池不允许继续添加线程,并且等待正在执行的线程返回。
调用awaitTermination设置定时任务,代码内的意思为 2s 后检测线程池内的线程是否均执行完毕(就像老师告诉学生,“最后给你 2s 钟时间把作业写完”),若没有执行完毕,则调用shutdownNow()方法。