线程池的优势:
- 降低资源消耗:通过池化技术重复利用已创建的线程,降低线程创建和销毁造成的损耗。
- 提高响应速度:任务到达时,无需等待线程创建即可立即执行。
- 提高线程的可管理性:线程是稀缺资源,如果无限制创建,不仅会消耗系统资源,还会因为线程的不合理分布导致资源调度失衡,降低系统的稳定性。使用线程池可以进行统一的分配、调优和监控。
- 提供更多更强大的功能:线程池具备可拓展性,允许开发人员向其中增加更多的功能。比如延时定时线程池ScheduledThreadPoolExecutor,就允许任务延期执行或定期执行。
线程池解决的问题:
线程池解决的核心问题就是资源管理问题。在并发环境下,系统不能够确定在任意时刻中,有多少任务需要执行,有多少资源需要投入。这种不确定性将带来以下若干问题:
- 频繁申请/销毁资源和调度资源,将带来额外的消耗,可能会非常巨大。
- 对资源无限申请缺少抑制手段,易引发系统资源耗尽的风险。
- 系统无法合理管理内部的资源分布,会降低系统的稳定性。
为解决资源分配这个问题,线程池采用了“池化”(Pooling)思想。池化,顾名思义,是为了最大化收益并最小化风险,而将资源统一在一起管理的一种思想。
java中提供的线程池:
Java中的线程池核心实现类是ThreadPoolExecutor,以下为ThreadPoolExecutor的UML类图。
ThreadPoolExecutor实现的顶层接口是Executor,顶层接口Executor提供了一种思想:将任务提交和任务执行进行解耦。用户无需关注如何创建线程,如何调度线程来执行任务,用户只需提供Runnable对象,将任务的运行逻辑提交到执行器(Executor)中,由Executor框架完成线程的调配和任务的执行部分。
ExecutorService接口增加了一些能力:
- 扩充执行任务的能力,补充可以为一个或一批异步任务生成Future的方法;
- 提供了管控线程池的方法,比如停止线程池的运行。
AbstractExecutorService则是上层的抽象类:
将执行任务的流程串联了起来,保证下层的实现只需关注一个执行任务的方法即可。最下层的实现类ThreadPoolExecutor实现最复杂的运行部分,ThreadPoolExecutor将会一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
Executors 类提供工厂方法用来创建不同类型的线程池:
- newCachedThreadPool
- newFixedThreadPool
- newScheduledThreadPool
- newSingleThreadExecutor
线程池的用法:
通过Executors里的方法创建线程池,这些方法最终都是通过ThreadPoolExecutor类来完成的。
1、newCachedThreadPool
创建一个可缓存的无界线程池,该方法无参数。当线程池中的线程空闲时间超过60s则会自动回收该线程,当任务超过线程池的线程数则创建新线程。线程池的大小上限为Integer.MAX_VALUE,可看做是无限大。
1 public void cachedThreadPoolDemo(){
2 ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
3 for (int i = 0; i < 5; i++) {
4 final int index = i;
5 cachedThreadPool.execute(new Runnable() {
6 @Override
7 public void run() {
8 System.out.println(Thread.currentThread().getName()+", index="+index);
9 }
10 });
11
12 try {
13 Thread.sleep(1000);
14 } catch (InterruptedException e) {
15 e.printStackTrace();
16 }
17 }
18 }
运行结果:
pool-1-thread-1, index=0
pool-1-thread-1, index=1
pool-1-thread-1, index=2
pool-1-thread-1, index=3
pool-1-thread-1, index=4
从运行结果可以看出,整个过程都在同一个线程pool-1-thread-1
中运行,后面线程复用前面的线程。
2、newFixedThreadPool
创建一个固定大小的线程池,该方法可指定线程池的固定大小,对于超出的线程会在LinkedBlockingQueue队列中等待。
1 public void fixedThreadPoolDemo(){
2 ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
3 for (int i = 0; i < 6; i++) {
4 final int index = i;
5 fixedThreadPool.execute(new Runnable() {
6 @Override
7 public void run() {
8 System.out.println(Thread.currentThread().getName()+", index="+index);
9 }
10 });
11 try {
12 Thread.sleep(1000);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 }
17 }
运行结果:
pool-1-thread-1, index=0
pool-1-thread-2, index=1
pool-1-thread-3, index=2
pool-1-thread-1, index=3
pool-1-thread-2, index=4
pool-1-thread-3, index=5
从运行结果可以看出,线程池大小为3,每休眠1s后将任务提交给线程池的各个线程轮番交错地执行。线程池的大小设置,可参数Runtime.getRuntime().availableProcessors()。
3、newScheduledThreadPool
创建一个可定时执行或周期执行任务的线程池,该方法可指定线程池的核心线程个数。
1 public void scheduledThreadPoolDemo(){
2 ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(3);
3 //定时执行一次的任务,延迟1s后执行
4 scheduledThreadPool.schedule(new Runnable() {
5 @Override
6 public void run() {
7 System.out.println(Thread.currentThread().getName()+", delay 1s");
8 }
9 }, 1, TimeUnit.SECONDS);
10 //周期性地执行任务,延迟2s后,每3s一次地周期性执行任务
11 scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
12 @Override
13 public void run() {
14 System.out.println(Thread.currentThread().getName()+", every 3s");
15 }
16 }, 2, 3, TimeUnit.SECONDS);
17 }
运行结果:
pool-1-thread-1, delay 1s
pool-1-thread-1, every 3s
pool-1-thread-2, every 3s
pool-1-thread-2, every 3s
...
ScheduledExecutorService功能强大,对于定时执行的任务,建议多采用该方法。
- schedule(Runnable command, long delay, TimeUnit unit),延迟一定时间后执行Runnable任务;
- schedule(Callable callable, long delay, TimeUnit unit),延迟一定时间后执行Callable任务;
- scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit),延迟一定时间后,以间隔period时间的频率周期性地执行任务;
- scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit),与scheduleAtFixedRate()方法很类似,但是不同的是scheduleWithFixedDelay()方法的周期时间间隔是以上一个任务执行结束到下一个任务开始执行的间隔,而scheduleAtFixedRate()方法的周期时间间隔是以上一个任务开始执行到下一个任务开始执行的间隔,也就是这一些任务系列的触发时间都是可预知的。
4、newSingleThreadExecutor
创建一个只有线程的线程池,该方法无参数,所有任务都保存队列LinkedBlockingQueue中,等待唯一的单线程来执行任务,并保证所有任务按照指定顺序(FIFO或优先级)执行。
1 public void singleThreadExecutorDemo(){
2 ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
3 for (int i = 0; i < 3; i++) {
4 final int index = i;
5 singleThreadExecutor.execute(new Runnable() {
6 @Override
7 public void run() {
8 System.out.println(Thread.currentThread().getName()+", index="+index);
9 }
10 });
11 try {
12 Thread.sleep(1000);
13 } catch (InterruptedException e) {
14 e.printStackTrace();
15 }
16 }
17 }
运行结果:
pool-1-thread-1, index=0
pool-1-thread-1, index=1
pool-1-thread-1, index=2
从运行结果可以看出,所有任务都是在单一线程运行的。