线程池介绍

线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。

为什么要使用线程池?

可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销。
避免创建大量同类线程所导致的资源过度竞争和内存溢出的问题。
支持更多功能,比如延迟任务线程池(newScheduledThreadPool)和缓存线程池(newCachedThreadPool)等。

线程池使用

创建线程池有两种方式:ThreadPoolExecutor 和 Executors,其中 Executors 又可以创建 6 种不同的线程池类型,会在下节讲,本节重点来看看 ThreadPoolExecutor 的使用。

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 参数说明

ThreadPoolExecutor 构造方法有以下四个,如下图所示:

1627571467158.jpg

其中最后一个构造方法有 7 个构造参数,包含了前三个方法的构造参数,这 7 个参数名称如下所示:

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

其代表的含义如下:

(1) corePoolSize

线程池中的核心线程数,默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。

(2) maximumPoolSize

最大线程数,当线程不够时能够创建的最大线程数。

(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;
    }
}

线程池执行方法 execute() VS submit()
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

线程池关闭

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

shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。
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() 之后就不会再接受新的任务了,不过之前的任务会被执行完成。

文章目录