一.为什么要使用线程池
1. 降低资源消耗
2. 减少反复创建线程带来的开销
3. 提高获取线程的响应速度
二.Executor框架
1.什么时Executor框架
Excutor框架与JDK 1.5被引入,用于代替线程的手动start启动,并且为我们提供了线程池的支持等等。
Executor 框架不仅包括了线程池的管理,还提供了线程工厂、队列以及拒绝策略等,Executor 框架让并发编程变得更加简单。
2.Executor框架的组成有哪些
主要可以分为三个部分:
- 任务(Runnable和Callable):任务是Executor提交执行的最小单位,只要实现了Runnable接口或Callable接口并重写相关方法即可被作为任务执行。
- 任务执行器(Executor):任务执行器的任务是提交任务(提交Callable任务还可获取到执行结果)。任务执行器需要实现ExecutorService接口,该接口继承自Executor抽象类。常见的任务执行器有ThreadPollExecutor和ScheduledThreadPollExecutor。
- 异步计算结果(Future)
Future 接口以及 Future 接口的实现类 FutureTask 类都可以代表异步计算的结果。
当我们把 Runnable接口 或 Callable 接口 的实现类提交给 ThreadPoolExecutor 或 ScheduledThreadPoolExecutor 执行。(调用 submit() 方法时会返回一个 FutureTask 对象)
三、ThreadPoolExecutor相关
ThreadPoolExecutor位于java.util.concurrent并发包下,为一些Executor提供了基本的实现,在JDK1.5中由Executors中的工厂中的newCachedThreadPool、newFixedThreadPool和newScheduledThreadExecutor方法所返回。但在JDK1.7之后,不能通过Executors去获得ThreadPoolExecutor了,而是需要自己手动去定制。ThreadPoolExecutor是一个灵活的、健壮的池实现,允许用户进行各种各样的定制。
1.ThreadPoolExecutor的构造方法参数
* 用给定的初始参数创建一个新的ThreadPoolExecutor。
*/
public ThreadPoolExecutor(int corePoolSize,//线程池的核心线程数量
int maximumPoolSize,//线程池的最大线程数
long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列,用来储存等待执行任务的队列
ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
)
- corePoolSize :
核心线程池大小,当线程池中的线程数目小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。 - maximumPoolSize:
最大线程池大小,表示当前池中可以活跃的最大线程数目。 - keepAliveTime :
线程池中超过corePoolSize数目的空闲线程最大存活时间。 - TimeUnit:
keepAliveTime时间单位。可以设置为TimeUnit.MICROSECONDS
、TimeUnit.SECONDS
等单位,分别表示毫秒和秒。 - workQueue :
阻塞任务队列,用以存储超出了corePoolSize的线程。 - threadFactory:
新建线程工厂。如果没有的话则使用系统默认的。 - RejectedExecutionHandler:
当提交任务数超过maxmumPoolSize与workQueue之和时,任务会交给RejectedExecutionHandler来处理。如果没有的话则使用系统默认的。
2.不断地往线程池中添加任务
- 当任务的数量增加时,会增加core线程来处理,但此时线程数量不会超过corePoolSize;
- 当core线程全部被占用着且已经达到了corePoolSize,此时不会立即创建新的线程,而是将任务扔到任务队列里面,并且用core线程去执行;
- 任务队列满了(意味着插入任务失败),则开始创建MAX线程去处理它们;若线程数达到MAX后,队列还一直是满的(无法处理),则抛出RejectedExecutionException。
3.ThreadPoolExecutor的饱和策略有哪些
首先明确一点,饱和策略会在ThreadPoolExecutor无法处理新增的任务时起作用,即线程数量已经达到了max,且当前的工作队列是满的,无法处理新的任务是:
1.AbortPolicy
抛出RejectedExecutionException异常,即无法处理异常。
2.CallerRunsPolicy
既不抛弃任务也不抛出异常,而是将某些任务回退到调用者,让调用者去执行它。
3.DiscardPolicy
将不能够处理的任务抛弃,不执行它。
4.DiscardOldestPolicy
将任务队列中最老的任务丢弃,而将新的任务放入任务队列中。
4.有哪些线程池
1.newFixedThreadPool
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(
nThreads,// corePoolSize
nThreads, // maxmiPoolSize
0L, TimeUnit.MILLISECONDS,
//使用一个基于FIFO排序的阻塞队列,在所有corePoolSize线程都忙时新任务将在队列中等待
new LinkedBlockingQueue<Runnable>());
}
创建可重用且固定线程数的线程池,如果线程池中的所有线程都处于活动状态,此时再提交任务就在队列中等待,直到有可用线程;如果线程池中的某个线程由于异常而结束时,线程池就会再补充一条新线程。原理是有界的阻塞队列。
2.newCachedThreadPool
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, // corePoolSize = 0
Integer.MAX_VALUE, // maximPoolSize=2^31 - 1
60L, TimeUnit.SECONDS, // 空闲时间60s
//使用同步队列,将任务直接提交给线程
new SynchronousQueue<Runnable>());
}
创建可缓存的线程池,如果线程池中的线程在60秒未被使用就将被移除,在执行新的任务时,当线程池中有之前创建的可用线程就重用可用线程,否则就新建一条线程。
3.newSingleThreadExecutor
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
//corePoolSize和maximumPoolSize都等于,表示固定线程池大小为1
(new ThreadPoolExecutor(1, // corePoolSize=1
1,// maximPoolSize=1
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
创建一个单线程的Executor,如果该线程因为异常而结束就新建一条线程来继续执行后续的任务。
4.newScheduledThreadPool
创建一个可延迟执行或定期执行的线程池。
5.如何确定线程池中需要多少个线程
一般来说,线程池中的线程数量不可盲目地确定和选择,因为如果线程池中线程数量太大,那么CPU会对许多线程进行上下文切换,从而导致效率的低下;如果线程数量太少,又会导致CPU核心数无法被充分地利用。
根据任务的不同,可将任务分为CPU密集型任务与IO密集型任务,它们的区别是CPU密集型任务需要充分地使用CPU操作,几乎不需要CPU等待;而IO密集型任务需要等待IO阻塞,所以CPU会有相对多的空闲时间。以下的N表示N核CPU:
1. CPU密集型任务:N + 1
2. IO密集型任务:2 * N
四、相关问题
1.Runnable与Callable
1. Runnable:无返回值的任务。且任务方法为run()
public interface Runnable {
/**
* 被线程执行,没有返回值也无法抛出异常
*/
public abstract void run();
}
2. Callable:有返回值的任务。且任务方法为V call()
public interface Callable<V> {
/**
* 计算结果,或在无法这样做时抛出异常。
* @return 计算得出的结果
* @throws 如果无法计算结果,则抛出异常
*/
V call() throws Exception;
}
2.execute()和submit()
- execute():提交的是Runnable,且不能接受返回值。
- submit():提交的是Callable,且能接受返回值FutureTask。
public Future<?> submit(Runnable task) {
if (task == null) throw new NullPointerException();
RunnableFuture<Void> ftask = newTaskFor(task, null);
execute(ftask);
return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
return new FutureTask<T>(runnable, value);
}
3.shutdown()和shutdownNow()
shutdown()和shutdownNow()内部实现是依次调用线程的interrupt方法去终止线程执行,但不同之处在于shutdown()是终止已经执行完成任务的线程而不会去终止正在执行任务的进程,但shutdownNow()会终止所有状态的线程,无论其是不是正在执行任务。
4.isTerminated()和isShutdown()
- isTerminated():当线程池中全部线程都已经关闭时,返回 true。
- isShutdown():当线程池调用shutdown()或shutdownNow()方法时,返回true。