什么是线程池:
线程池(Thread Pool):把一个或多个线程通过统一的方式进行调度和重复使用的技术,避免了因为线程过多而带来使用上的开销。
为什么要使用线程池?
- 可重复使用已有线程,避免对象创建、消亡和过度切换的性能开销。
- 避免创建大量同类线程所导致的资源过度竞争和内存溢出的问题。
- 支持更多功能,比如延迟任务线程池(newScheduledThreadPool)和缓存线程池(newCachedThreadPool)等。
线程池的使用
创建线程池有两种方式:ThreadPoolExecutor 和 Executors,其中 Executors 又可以创建 6 种不同的线程池类型。
线程池的工作原理
当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心线程数就会新建线程进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行拒绝执行策略。
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 的参数说明
TheadPoolExecutor 构造方法有以下四个:
最后一个构造方法需要的参数最多,并包含了前面三个方法。下面是最后一个构造方法所需要的 7 个函数:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
{
// ...
}
各个函数所代表的的意义:
- corePoolSize:线程池中的核心线程数。默认情况下核心线程一直存活在线程池中,如果将 ThreadPoolExecutor 的 allowCoreThreadTimeOut 属性设为 true,如果线程池一直闲置并超过了 keepAliveTime 所指定的时间,核心线程就会被终止。
- maximumPoolSize:最大线程数,当线程不够时能够创建的最大线程数。
- keepAliveTime:线程池的闲置超时时间,默认情况下对非核心线程生效,如果闲置时间超过这个时间,非核心线程就会被回收。如果 ThreadPoolExecutor 的 allowCoreThreadTimeOut 设为 true 的时候,核心线程如果超过闲置时长也会被回收。
- unit:配合 keepAliveTime 使用,用来标识 keepAliveTime 的时间单位。TimeUnit 枚举类提供了很多单位,如:纳秒、微秒、毫秒、秒、分、时、天。
- workQueue:线程池中的任务队列,使用 execute() 或 submit() 方法提交的任务都会存储在此队列中。
- threadFactory:为线程池提供创建新线程的线程工厂。
- rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略,RejectedExecutionHandler 是一个接口,里面只有一个 rejectedExecution 方法,可在此方法内添加任务超出最大值的事件处理。ThreadPoolExecutor 也提供了 4 种默认的拒绝策略:
- new ThreadPoolExecutor.DiscardPolicy():丢弃掉该任务,不进行处理
- new ThreadPoolExecutor.DiscardOldestPolicy():丢弃队列里最近的一个任务,并执行当前任务
- new ThreadPoolExecutor.AbortPolicy():直接抛出 RejectedExecutionException 异常
- new ThreadPoolExecutor.CallerRunsPolicy():既不抛弃任务也不抛出异常,直接使用主线程来执行此任务
包含所有参数的 ThreadPoolExecutor 使用代码:
public class ThreadPoolStudy {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
10L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(2), new MyThreadFactory(),
new ThreadPoolExecutor.CallerRunsPolicy());
threadPoolExecutor.allowCoreThreadTimeOut(true);
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(() -> {
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 thread = new Thread(r);
String threadName = "MyThread" + count.addAndGet(1);
thread.setName(threadName);
return thread;
}
}
线程池的执行方法
线程池的执行方法有两种:execute() 和 submit() ,它们的区别在于 submit() 方法可以接收线程池执行的返回值。
如下所示:
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(2, 10, 10L, TimeUnit.SECONDS, new LinkedBlockingQueue(100));
// submit 使用
Future<String> future = threadPoolExecutor.submit(() -> {
System.out.println("Hello, 老王.");
return "Success";
});
try {
System.out.println(future.get());
// 程序不会退出,需要加退出代码
System.exit(0);
} catch (ExecutionException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
以上程序执行结果如下:
Hello, 老王.
Success
线程池的关闭
线程池关闭,可以使用 shutdown() 或 shutdownNow() 方法,它们的区别是:
- shutdown():不会立即终止线程池,而是要等所有任务队列中的任务都执行完后才会终止。执行完 shutdown 方法之后,线程池就不会再接受新任务了。
- shutdownNow():执行该方法,线程池的状态立刻变成 STOP 状态,并试图停止所有正在执行的线程,不再处理还在池队列中等待的任务,执行此方法会返回未执行的任务。
拓展:
1.ThreadPoolExecutor 有哪些常用的方法?
- submit()/execute():执行线程池
- shutdown()/shutdownNow():终止线程池
- isShutdown():判断线程是否终止
- getActiveCount():正在运行的线程数
- getCorePoolSize():获取核心线程数
- getMaximumPoolSize():获取最大线程数
- getQueue():获取线程池中的任务队列
- allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程
总结:
ThreadPoolExecutor 是创建线程池最传统和最推荐使用的方式,创建时要设置线程池的核心线程数和最大线程数还有任务队列集合,如果任务量大于队列的最大长度,线程池会先判断当前线程数量是否已经到达最大线程数,如果没有达到最大线程数就新建线程来执行任务,如果已经达到最大线程数,就会执行拒绝策略(拒绝策略可自行定义)。线程池可通过 submit() 来调用执行,从而获得线程执行的结果,也可以通过 shutdown() 来终止线程池。
文档内容转载自《老王的 GitChat 专栏》:https://gitbook.cn/gitchat/column/5d493b4dcb702a087ef935d9