线程池的作用
1、减少线程创建与切换的开销
- 在没有使用线程池的时候,来了一个任务,就创建一个线程,我们知道系统创建和销毁工作线程的开销很大,
- 而且频繁的创建线程也就意味着需要进行频繁的线程切换,这都是一笔很大的开销。
2、控制线程的数量
- 使用线程池我们可以有效地控制线程的数量,当系统中存在大量并发线程时,会导致系统性能剧烈下降。
线程池工作原理
循环利用有限的线程
- 线程池中会预先创建一些空闲的线程,他们不断的从工作队列中取出任务,然后执行,执行完之后会继续执行工作队列中的下一个任务,减少了创建和销毁线程的次数,每个线程都可以一直被重用,节省创建和销毁的开销。
线程池的使用
常用Java线程池本质上都是由ThreadPoolExecutor或者ForkJoinPool生成的,只是其根据构造函数传入不同的实参来实例化相应线程池而已。
Executors
Executors是一个线程池工厂类,该工厂类包含如下集合静态工厂方法来创建线程池:
- newFixedThreadPool():创建一个可重用的、具有固定线程数的线程池
- newSingleThreadExecutor():创建只有一个线程的线程池
- newCachedThreadPool():创建一个具有缓存功能的线程池
- newWorkStealingPool():创建持有足够线程的线程池来支持给定的并行级别的线程池
- newScheduledThreadPool():创建具有指定线程数的线程池,它可以在指定延迟后执行任务线程
ExecutorService接口
Java线程池也采用了面向接口编程的思想,可以看到ThreadPoolExecutor和ForkJoinPool所有都是ExecutorService接口的实现类
在ExecutorService接口中定义了一些常用的方法,然后再各种线程池中都可以使用ExecutorService接口中定义的方法,常用的方法有如下几个:
向线程池提交线程
- Future> submit():将一个Runnable对象交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务,该方法既能接收Runnable对象也能接收Callable对象,这就意味着sumbit()方法可以有返回值。
- void execute(Runnable command):只能接收Runnable对象,意味着该方法没有返回值。
关闭线程池
- void shutdown():阻止新来的任务提交,对已经提交了的任务不会产生任何影响。(等待所有的线程执行完毕才关闭)
- List shutdownNow(): 阻止新来的任务提交,同时会中断当前正在运行的线程,另外它还将workQueue中的任务给移除,并将这些任务添加到列表中进行返回。(立马关闭)
检查线程池状态
- boolean isShutdown():调用shutdown()或shutdownNow()方法后返回为true
- boolean isTerminated():当调用shutdown()方法后,并且所有提交的任务完成后返回为true;当调用shutdownNow()方法后,成功停止后返回为true。
常见线程池使用示例
1.newFixedThreadPool
线程池中的线程数目是固定的,不管来了多少的任务
运行结果:
- 当创建好一个FixedThreadPool之后,该线程池就处于Running状态了,但以下四个参数都是0
- pool size(线程池线程的数量)
- active threads(当前活跃线程)
- queued tasks(当前排队线程)
- completed tasks(已完成的任务数)
- 当把6个任务都提交给线程池之后
- pool size = 5:因为我们创建的是一个固定线程数为5的线程池(注意:如果这个时候我们只提交了3个任务,那么pool size = 3,说明线程池也是通过懒加载的方式去创建线程)
- active threads = 5:虽然向线程池提交了6个任务,但是线程池的固定大小为5,所以活跃线程只有5个
- queued tasks = 1:虽然向线程池提交了6个任务,但是线程池的固定大小为5,只能有5个活跃线程同时工作,所以有一个任务在等待
- 第一次执行shutdown()的时候,由于任务还没有全部执行完毕,所以isTerminated()返回false,shutdown()返回true,而线程池的状态会由Running变为Shutting down
- 从任务的运行结果我们可以看出,名为pool-1-thread-2执行了两次任务,证明线程池中的线程确实是重复利用的
- 5秒钟后,isTerminated()返回true,shutdown()返回true,证明所有的任务都执行完了,线程池也关闭了
- 再次检查线程池的状态[Terminated, pool size = 0, active threads = 0, queued tasks = 0, completed tasks = 6],状态已经处于Terminated了,然后已完成的任务显示为6
2.newSingleThreadExecutor
从头到尾整个线程池都只有一个线程在工作
输出结果
可以看到只有pool-1-thread-1一个线程在工作
3.newCachedThreadPool
来多少任务,就创建多少线程(前提是没有空闲的线程在等待执行任务,否则还是会复用之前旧(缓存)的线程),直接你电脑能支撑的线程数的极限为止
代码
运行结果
程序分析
- 因为我们每个线程任务至少需要500毫秒的执行时间,所以当我们往线程池中提交12个任务的过程中
- 基本上没有空闲的线程供我们重复使用,所以线程池会创建12个线程。
- 缓存中的线程默认是60秒没有活跃就会被销毁掉,可以看到在50秒钟的时候回,所有的任务已经完成了,但是线程池线程的数量还是12。
- 80秒过后,可以看到线程池中的线程已经全部被销毁了。
4.newScheduledThreadPool
可以在指定延迟后或周期性地执行线程任务的线程池
ScheduledThreadPoolExecutor
- newScheduledThreadPool()方法返回的其实是一个ScheduledThreadPoolExecutor对象,ScheduledThreadPoolExecutor定义如下:
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
- 它还是继承了ThreadPoolExecutor并实现了ScheduledExecutorService接口
- 而ScheduledExecutorService也是继承了ExecutorService接口,
- 所以我们也可以像使用之前的线程池对象一样使用,只不过是该对象会额外多了一些方法用于控制延迟与周期:
- public ScheduledFuture schedule(Callable callable,long delay, TimeUnit unit):指定callable任务将在delay延迟后执行
- public ScheduledFuture> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit):指定的command任务将在delay延迟后执行,而且已设定频率重复执行。(一开始并不会执行)
- public ScheduledFuture> scheduleWithFixedDelay(Runnable command,ong initialDelay,long delay,TimeUnit unit):创建并执行一个在给定初始延迟后首期启用的定期操作,随后在每一个执行终止和下一次执行开始之间都存在给定的延迟。
下面代码每500毫秒打印一次当前线程名称以及一个随机数字。
输出
5.newWorkStealingPool
每个线程维护着自己的队列,执行完自己的任务之后,会去主动执行其他线程队列中的任务
运行结果
ForkJoinPool-1-worker-1任务的执行时间是1秒,它会最先执行完毕,然后它会去主动执行其他线程队列中的任务
6.ForkJoinPool
- ForkJoinPool可以将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果
- ForkJoinPool提供了如下几个方法用于创建ForkJoinPool实例对象:
- ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool,parallelism的默认值为Runtime.getRuntime().availableProcessors()方法的返回值
- ForkJoinPool commonPool():该方法返回一个通用池,通用池的运行状态不会受shutdown()或shutdownNow()方法的影响。
- 创建了ForkJoinPool示例之后,就可以调用ForkJoinPool的submit(ForkJoinTask task)或invoke(ForkJoinTask task)方法来执行指定任务了。其中ForkJoinTask(实现了Future接口)代表一个可以并行、合并的任务。ForkJoinTask是一个抽象类,他还有两个抽象子类:RecursiveAction和RecursiveTask。其中RecursiveTask代表有返回值的任务,而RecursiveAction代表没有返回值的任务。
下面代码演示了使用ForkJoinPool对1000000个随机整数进行求和
输出: