线程池简介
线程并发库所在的位置在java.util.current包中,该包提供了线程的运行,线程池的创建,线程的生命周期的控制。
线程池的种类
1. newCacheThreadPool 可缓存线程池
2. newFixedThreadPool 定长线程池,可控制线程最大数
3. newScheduledThreadPool 定长线程池,支持定时及周期性任务执行
4. newSingleThreadExecutor 单线程化线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO,LIFO优先级)执行
线程池的作用
- 通过限定线程池中线程的个数,防止资源不足,系统运行缓慢或崩溃
- 节约了资源,创建线程需要一定的开销
- 当有任务过来时,线程已经存在,不用再去创建线程,响应时间更快
线程池源码解析
/**
* 创建一个大小为5的线程池,注意在创建了线程池之后,默认情况下,线程池中没有任何线程,
* 而是等待有任务过来时才创建线程去执行任务
*/
ExecutorService exec= Executors.newFixedThreadPool(5);
new ThreadPoolExecutor(nThreads, nThreads, 0L,TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);
参数说明:
int corePoolSize:
核心线程池的大小,默认情况下,在创建了线程池后,线程池中的线程数为0,来一个任务就创建一个线程,当线程池中的线程数目达到corePoolSize后,就把到达的任务放到缓存队列中。
int maximumPoolSize:
线程池最大线程数,它表示在线程池中最多能创建多少个线程
long keepAliveTime:
当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,就会把这个线程从线程池中销毁掉,直到线程池中的线程数不超过corePoolSize
TimeUnit unit:
keepAliveTime的时间单位
BlockingQueue<Runnable> workQueue:
一个阻塞队列,用来存储等待执行的任务,这个参数的选择很重要,会对线程池的运行过程产生重大影响
ThreadFactory threadFactory:
线程工厂,主要用来创建线程
RejectedExecutionHandler handler:任务拒绝策略
ThreadPoolExecutor继承了AbstractExecutorService
AbstractExecutorService实现了ExecutorService接口
ExecutorService接口继承了Executor接口
Executor接口中有一个execute的方法
ThreadPoolExecutor源码剖析
- ThreadPoolExecutor中几个重要的方法
- execute()方法
2. submit()方法
- execute()方法
execute(Runnable command):
execute()方法实际上是Executor中声明的方法,在ThreadPoolExecutor进行了具体的实现,这个方法是ThreadPoolExecutor的核心方法,通过这个方法可以向线程池提交一个任务,交由线程池去执行。
Future<?> submit(Runnable task):
submit()方法是在ExecutorService中声明的方法,AbstractExecutorService就已经有了具体的实现,在ThreadPoolExecutor中并没有对其进行重写,这个方法也是用来向线程池提交任务的,但是它和execute()方法不同,它能够返回任务执行的结果,去看submit()方法的实现,会发现它实际上还是调用的execute()方法,只不过它利用了Future来获取任务执行结果。
shutdown()和shutdownNow():
shutdown()和shutdownNow()是用来关闭线程池的。
- 当把一个任务交给线程池来处理的时候,线程池的执行原理如下图所示
①首先会判断核心线程池里是否有线程可执行,有空闲线程则创建一个线程来执行任务。
②当核心线程池里已经没有线程可执行的时候,此时将任务丢到任务队列中去。
③如果任务队列(有界)也已经满了的话,但运行的线程数小于最大线程池的数量的时候,此时将会新建一个线程用于执行任务,但如果运行的线程数已经达到最大线程池的数量的时候,此时将无法创建线程执行任务。
所以实际上对于线程池不仅是单纯地将任务丢到线程池,线程池中有线程就执行任务,没线程就等待。
- ThreadPoolExecutor类中两个变量corePoolSize和maximumPoolSize的含义?
假如有一个工厂,工厂里面有10个工人,每个工人同时只能做一件任务。因此只要当10个工人中有工人是空闲的,来了任务就分配给空闲的工人做; 当10个工人都有任务在做时,如果还来了任务,就把任务进行排队等待;如果说新任务数目增长的速度远远大于工人做任务的速度,那么此时工厂主管可能会想补救措施,比如重新招4个临时工人进来;然后就将任务也分配给这4个临时工人做;如果说着14个工人做任务的速度还是不够,此时工厂主管可能就要考虑不再接收新的任务或者抛弃前面的一些任务了。当这14个工人当中有人空闲时,而新任务增长的速度又比较缓慢,工厂主管可能就考虑辞掉4个临时工了,只保持原来的10个工人,毕竟请额外的工人是要花钱的。这个例子中的corePoolSize就是10,而maximumPoolSize就是14(10+4)。也就是说corePoolSize就是线程池大小,maximumPoolSize在我看来是线程池的一种补救措施,即任务量突然过大时的一种补救措施。
- 为什么用线程池替代以前的创建一个线程(new Thread())呢?
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,如果使用以前new Thread()的方法,就要频繁创建线程,会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。解决办法就是使用线程池,最开始创建多个线程(有多种创建线程的方式,见四中)丢进一个大池子里,来一个任务就从池子中拿出一个线程执行这个任务,执行完了,这个线程不被销毁,而是再丢回池子中去,下一次可以继续执行其他的任务,重复使用。
- 创建一个线程池的几种方式?
newFixedThreadPool(int threads):
创建一个固定大小的线程池,当池中线程的数量还未达到corePoolSize,每提交一个任务就创建一个线程去执行它,线程执行完不销毁,直接丢到池里,供重复使用,当池中线程的数量达到corePoolSize,将之后提交的任务存入到池队列中等待被处理。
newCachedThreadPool():
创建一个可缓存的线程池,这类线程池的特点是,如果线程池中的线程数远远大于待处理任务数,将自动回收空闲线程;当任务数增加,线程池中的线程不够用时,则可以自动添加新线程。
newSingleThreadExecutor():
创建单个工作线程来执行任务,如果这个线程异常结束,会创建一个新的来替代它,它的特点是能确保依照任务在队列中的顺序来串行执行。
newScheduledThreadPool(int corePoolSize):
创建了一个固定长度的线程池,而且以延迟或定时的方式来执行任务,类似于Timer。
- Callable、Future和FutureTask三个类的使用方法
-
使用Callable接口创建线程和使用Thread、Runnable有何不同?
在前面的文章中我们讲述了创建线程的2种方式,一种是直接继承Thread,另外一种就是实现Runnable接口。这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。解决办法就是使用Callable接口。
看源码发现,Runnable接口中的run()方法的返回值是void类型,而Callable接口的call()方法的返回值类型就是传递进来的V类型。
- Runnable接口和Callable接口
- Future接口:
三种功能:(1)判断任务是否完成;(2)能够中断任务;(3)能够获取任务执行结果。
-
Callable接口怎么使用?
一般情况下是配合ExecutorService来使用的,在ExecutorService接口中声明了若干个submit方法的重载版本
- submit方法的版本
-
Future和FutureTask的关系
- FutureTask是Future接口的一个唯一实现类。
- FutureTask类实现了RunnableFuture接口,RunnableFuture接口详情如下:
- FutureTask
- get()方法:用来获取执行结果,这个方法会产生阻塞,会一直等到任务执行完毕才返回;
- get(long timeout, TimeUnit unit):用来获取执行结果,如果在指定时间内,还没获取到结果,就直接返回null。
- isDone方法:表示任务是否已经完成,若任务完成,则返回true;
- 什么叫get()阻塞,什么是阻塞式方法?
- 程序运行到get()就不运行了,等待有条件触发才运行,这个条件就是计算完成得到计算结果。阻塞式方法是指程序会一直等待该方法完成,期间不做其他事情。
-
用Future接口的get()获得计算得到的结果,有以下几种方式:
1、先用isDone()查询Future是否计算完得到结果,如果isDone()返回true,说明计算完了,就可以使用get()去获得计算结果,如果isDone()返回false,说明计算还在进行,等到计算完成,再调用get()。
2、不用isDone()检查就直接调用get()方法,在这种情况下,get()将阻塞,直至计算完成得到计算结果。
参考博客: