什么是线程池(Thread Pool)?
为了避免系统频繁地创建和销毁线程,我们可以让创建的线程进行复用。 用线程时从线程池中获取,用完以后不销毁线程,而是归还给线程池(Thread Pool)。
为什么需要使用线程池
减少线程创建与切换的开销
在没有使用线程池的时候,来了一个任务,就创建一个线程,我们知道系统创建和销毁工作线程的开销很大,而且频繁的创建线程也就意味着需要进行频繁的线程切换,这都是一笔很大的开销。
控制线程的数量
使用线程池我们可以有效地控制线程的数量,当系统中存在大量并发线程时,会导致系统性能剧烈下降。
线程池做了什么
重复利用有限的线程
线程池中会预先创建一些空闲的线程,他们不断的从工作队列中取出任务,然后执行,执行完之后,会继续执行工作队列中的下一个任务,减少了创建和销毁线程的次数,每个线程都可以一直被重用,变了创建和销毁的开销。
JDK 对线程池的支持
为了更好的控制多线程,JDK 提供了一套线程池框架。它们都在 java.util.concurrent 包中。
- Executor 用来执行任务,它提供了 execute() 方法来执行 Runnable 任务;
- ThreadPoolExecutor 表示一个线程池;
- Executors 是一个线程池工厂类,该工厂类包含如下集合静态工厂方法来创建线程池:
newFixedThreadPool()
:创建一个可重用的、具有固定线程数的线程池newSingleThreadExecutor()
:创建只有一个线程的线程池newCachedThreadPool()
:创建一个具有缓存功能的线程池newWorkStealingPool()
:创建持有足够线程的线程池来支持给定的并行级别的线程池newScheduledThreadPool()
:创建具有指定线程数的线程池,它可以在指定延迟后执行任务线程
newFixedThreadPool()
newFixedThreadPool() 返回固定数量的线程池。线程池中的数量始终不变。当有新任务提交时,线程池中若有空闲线程,则立即执行。若没有则放入任务队列中,等待有空闲线程时,处理任务队列中的任务。
实例代码
public class MyFixThreadPool {
public static void main(String[] args) throws InterruptedException {
// 创建一个线程数固定为5的线程池
ExecutorService service = Executors.newFixedThreadPool(5);
System.out.println("初始线程池状态:" + service);
for (int i = 0; i < 6; i++) {
service.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println("线程提交完毕之后线程池状态:" + service);
service.shutdown();//会等待所有的线程执行完毕才关闭,shutdownNow:立马关闭
System.out.println("是否全部线程已经执行完毕:" + service.isTerminated());//所有的任务执行完了,就会返回true
System.out.println("是否已经执行shutdown()" + service.isShutdown());
System.out.println("执行完shutdown()之后线程池的状态:" + service);
TimeUnit.SECONDS.sleep(5);
System.out.println("5秒钟过后,是否全部线程已经执行完毕:" + service.isTerminated());
System.out.println("5秒钟过后,是否已经执行shutdown()" + service.isShutdown());
System.out.println("5秒钟过后,线程池状态:" + service);
}
}
运行结果
初始线程池状态:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0]
线程提交完毕之后线程池状态:[Running, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
是否全部线程已经执行完毕:false
是否已经执行shutdown():true
执行完shutdown()之后线程池的状态:[Shutting down, pool size = 5, active threads = 5, queued tasks = 1, completed tasks = 0]
pool-1-thread-2
pool-1-thread-1
pool-1-thread-4
pool-1-thread-5
pool-1-thread-3
pool-1-thread-2
5秒钟过后,是否全部线程已经执行完毕:true
5秒钟过后,是否已经执行shutdown():true
5秒钟过后,线程池状态:[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 6]
newSingleThreadPool()
newSingleThreadPool() 返回只有一个线程的线程池。若有多余一个任务被提交,则放入任务队列中,等待有空闲线程时,按先入先出的顺序执行队列中的任务。
实例代码
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
final int j = i;
service.execute(() -> {
System.out.println(j + " " + Thread.currentThread().getName());
});
}
}
}
复制代码
运行结果
0 pool-1-thread-1 1 pool-1-thread-1 2 pool-1-thread-1 3 pool-1-thread-1 4 pool-1-thread-1
程序分析
可以看到只有pool-1-thread-1
一个线程在工作。
newCachedThreadPool()
newCachedThreadPool() 返回一个可根据实际情况调整线程数量的线程池。这个方法创建出来的线程池可以被无限扩展,并且当需求降低时会自动收缩。
实例代码
public class CachePool {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newCachedThreadPool();
System.out.println("初始线程池状态:" + service);
for (int i = 0; i < 12; i++) {
service.execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
});
}
System.out.println("线程提交完毕之后线程池状态:" + service);
TimeUnit.SECONDS.sleep(50);
System.out.println("50秒后线程池状态:" + service);
TimeUnit.SECONDS.sleep(30);
System.out.println("80秒后线程池状态:" + service);
}
}
运行结果
初始线程池状态:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 0] 线程提交完毕之后线程池状态:[Running, pool size = 12, active threads = 12, queued tasks = 0, completed tasks = 0] pool-1-thread-3 pool-1-thread-4 pool-1-thread-1 pool-1-thread-2 pool-1-thread-5 pool-1-thread-8 pool-1-thread-9 pool-1-thread-12 pool-1-thread-7 pool-1-thread-6 pool-1-thread-11 pool-1-thread-10 50秒后线程池状态:[Running, pool size = 12, active threads = 0, queued tasks = 0, completed tasks = 12] 80秒后线程池状态:[Running, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 12]
程序分析
- 因为我们每个线程任务至少需要500毫秒的执行时间,所以当我们往线程池中提交12个任务的过程中,基本上没有空闲的线程供我们重复使用,所以线程池会创建12个线程。
- 缓存中的线程默认是60秒没有活跃就会被销毁掉,可以看到在50秒钟的时候回,所有的任务已经完成了,但是线程池线程的数量还是12。
- 80秒过后,可以看到线程池中的线程已经全部被销毁了。
newScheduledThreadPool()
newScheduledThreadPool() 返回一个 ScheduledExecutorService 对象,并可指定线程数量。
示例代码
下面代码每500毫秒打印一次当前线程名称以及一个随机数字。
public class MyScheduledPool {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(4);
service.scheduleAtFixedRate(() -> {
System.out.println(Thread.currentThread().getName() + new Random().nextInt(1000));
}, 0, 500, TimeUnit.MILLISECONDS);
}
}
Reference
- Java多线程系列——线程池简介 - https://www.cnblogs.com/zhengbin/p/7750077.htmls
- Java线程池了解一下 - https://juejin.im/post/5c5259ca51882524a76727db
- 线程池的介绍及简单实现 - https://www.ibm.com/developerworks/cn/java/l-threadPool/index.html