线程池的作用

1、减少线程创建与切换的开销

  • 在没有使用线程池的时候,来了一个任务,就创建一个线程,我们知道系统创建和销毁工作线程的开销很大,
  • 而且频繁的创建线程也就意味着需要进行频繁的线程切换,这都是一笔很大的开销。

2、控制线程的数量

  • 使用线程池我们可以有效地控制线程的数量,当系统中存在大量并发线程时,会导致系统性能剧烈下降。

线程池工作原理

循环利用有限的线程

  • 线程池中会预先创建一些空闲的线程,他们不断的从工作队列中取出任务,然后执行,执行完之后会继续执行工作队列中的下一个任务,减少了创建和销毁线程的次数,每个线程都可以一直被重用,节省创建和销毁的开销。

线程池的使用

常用Java线程池本质上都是由ThreadPoolExecutor或者ForkJoinPool生成的,只是其根据构造函数传入不同的实参来实例化相应线程池而已。

Executors

Executors是一个线程池工厂类,该工厂类包含如下集合静态工厂方法来创建线程池:

  • newFixedThreadPool():创建一个可重用的、具有固定线程数的线程池
  • newSingleThreadExecutor():创建只有一个线程的线程池
  • newCachedThreadPool():创建一个具有缓存功能的线程池
  • newWorkStealingPool():创建持有足够线程的线程池来支持给定的并行级别的线程池
  • newScheduledThreadPool():创建具有指定线程数的线程池,它可以在指定延迟后执行任务线程

ExecutorService接口

Java线程池也采用了面向接口编程的思想,可以看到ThreadPoolExecutorForkJoinPool所有都是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

线程池中的线程数目是固定的,不管来了多少的任务




java线程池执行超时设置 java线程池延迟执行_java 线程


运行结果:


java线程池执行超时设置 java线程池延迟执行_java 线程池使用_02


  • 当创建好一个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

从头到尾整个线程池都只有一个线程在工作


java线程池执行超时设置 java线程池延迟执行_java 线程池_03


输出结果


java线程池执行超时设置 java线程池延迟执行_java线程池执行超时设置_04


可以看到只有pool-1-thread-1一个线程在工作

3.newCachedThreadPool

来多少任务,就创建多少线程(前提是没有空闲的线程在等待执行任务,否则还是会复用之前旧(缓存)的线程),直接你电脑能支撑的线程数的极限为止

代码


java线程池执行超时设置 java线程池延迟执行_java 线程池_05


运行结果


java线程池执行超时设置 java线程池延迟执行_java 线程_06


程序分析

  • 因为我们每个线程任务至少需要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毫秒打印一次当前线程名称以及一个随机数字。


java线程池执行超时设置 java线程池延迟执行_java 延迟执行_07


输出


java线程池执行超时设置 java线程池延迟执行_java 线程池_08


5.newWorkStealingPool

每个线程维护着自己的队列,执行完自己的任务之后,会去主动执行其他线程队列中的任务


java线程池执行超时设置 java线程池延迟执行_java 线程池_09


运行结果


java线程池执行超时设置 java线程池延迟执行_java线程池执行超时设置_10


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个随机整数进行求和


java线程池执行超时设置 java线程池延迟执行_java 线程池使用_11


输出:


java线程池执行超时设置 java线程池延迟执行_java 线程池使用_12