# ThreadPoolExecutor

# 1. ThreadPoolExecutor 的使用

线程池使用代码如下:

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    2, 10, 
    10L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue(100));

threadPoolExecutor.execute(new Runnable() {
    @Override
    public void run() {
        // 执行线程池
        System.out.println("Hello, Java.");
    }
});

// 以上程序执行结果如下:
// Hello, Java.

ThreadPoolExecutor 构造方法有 4 个,其中参数最多的那个构造方法有 7 个入参,其它 3 个构造方法本质上就是这个『最长构造方法』的简写。

这 7 个参数名称如下所示:

public ThreadPoolExecutor(
        int corePoolSize,   // ①
        int maximumPoolSize,// ② 
        long keepAliveTime, // ③ 
        TimeUnit unit,      // ④
        BlockingQueue<Runnable> workQueue,  // ⑤
        ThreadFactory threadFactory,        // ⑥
        RejectedExecutionHandler handler    // ⑦
    ) {
        // ...
}

其代表的含义如下:

  1. corePoolSize

    线程池中的核心线程数,默认情况下核心线程一直存活在线程池中。即,逻辑上,线程池中的最少线程数。

  2. maximumPoolSize

    线程池中最大线程数。如果活动的线程达到这个数值以后,ThreadPoolExcecutor 再接收到新任务时,将会阻塞等待,而非创建线程立即执行。

  3. keepAliveTime

    线程池中的线程可闲置的最大时长,默认情况下对非核心线程生效。如果闲置时间超过这个时间,非核心线程就会被回收。

    如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true ,那么,核心线程也会受该时长影响。

  4. unit

    配合 keepAliveTime 使用,keepAliveTime 的值的时间单位。

  5. workQueue

    线程池中的任务队列,使用 execute 方法或 submit 方法提交的任务都会存储在此队列中。

  6. threadFactory

    为线程池提供创建新线程的线程工厂。

  7. rejectedExecutionHandler

    线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution 方法,可在此方法内添加任务超出最大值的事件处理。

    ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:

    • new ThreadPoolExecutor.DiscardPolicy():丢弃掉该任务,不进行处理
    • new ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列里最近的一个任务,并执行当前任务
    • new ThreadPoolExecutor.AbortPolicy():直接抛出 RejectedExecutionException 异常
    • new ThreadPoolExecutor.CallerRunsPolicy():既不抛弃任务也不抛出异常,直接使用主线程来执行此任务

包含所有参数的 ThreadPoolExecutor 使用代码示例:

public class ThreadPoolExecutorTest {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
                    1, 1,
                    10L, TimeUnit.SECONDS, 
                    new LinkedBlockingQueue<Runnable>(2),
                    new MyThreadFactory(), 
                    new ThreadPoolExecutor.CallerRunsPolicy());

        threadPool.allowCoreThreadTimeOut(true);
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());
                    try {
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
}

class MyThreadFactory implements ThreadFactory {
    private AtomicInteger count = new AtomicInteger(0);
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            String threadName = "MyThread" + count.addAndGet(1);
            t.setName(threadName);
            return t;
    }
}

# 2. 线程池执行方法

execute 方法和 submit 方法都是用来执行线程池的。它们的区别在于 submit 方法可以接收线程池执行的返回值。

下面分别来看两个方法的具体使用和区别:

// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
    2, 10, 
    10L, TimeUnit.SECONDS, 
    new LinkedBlockingQueue(100));
    // execute 使用
    threadPoolExecutor.execute(new Runnable() {
        @Override   // 逻辑代码
        public void run() {
            System.out.println("Hello, Java.");
        }
});

// submit 使用
Future<String> future = threadPoolExecutor.submit(new Callable<String>() {
    @Override
    public String call() throws Exception {
        System.out.println("Hello, 老王.");
        return "Success";
    }
});

System.out.println(future.get());

以上程序执行结果如下:

Hello, Java.
Hello, 老王.
Success

# 3. 线程池关闭方法

线程池关闭,可以使用 .shutdown.shutdownNow 方法,它们的区别是:

  1. .shutdown 方法:不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。

  2. .shutdownNow 方法:执行该方法,线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。

下面用代码来模拟 shutdown() 之后,给线程池添加任务,代码如下:

threadPoolExecutor.execute(() -> {
    for (int i = 0; i < 2; i++) {
        System.out.println("I'm " + i);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            System.out.println(e.getMessage());
        }
    }
});
threadPoolExecutor.shutdown();
threadPoolExecutor.execute(() -> {
    System.out.println("I'm Java.");
});

以上程序执行结果如下:

I'm 0

Exception in thread "main" java.util.concurrent.RejectedExecutionException:
Task com.interview.chapter5.Section2`$$Lambda$2`/1828972342@568db2f2 rejected
from java.util.concurrent.ThreadPoolExecutor@378bf509[Shutting down, pool size
= 1, active threads = 1, queued tasks = 0, completed tasks = 0]

I'm 1

可以看出,shutdown() 之后就不会再接受新的任务了,不过之前的任务会被执行完成。

# 4. 总结

ThreadPoolExecutor 是创建线程池最传统和最推荐使用的方式,创建时要设置线程池的核心线程数和最大线程数还有任务队列集合,如果任务量大于队列的最大长度,线程池会先判断当前线程数量是否已经到达最大线程数,如果没有达到最大线程数就新建线程来执行任务,如果已经达到最大线程数,就会执行拒绝策略(拒绝策略可自行定义)。

线程池可通过 submit() 来调用执行,从而获得线程执行的结果,也可以通过 shutdown() 来终止线程池。