四种线程池
- newCachedThreadPool具有缓存性质的线程池,线程最大空闲时间60s,线程可重复利用(缓存特性),没有最大线程数限制。任务耗时短,数量大
- 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
- newFixedThreadPool具有固定数量的线程池,核心线程数等于最大线程数,线程最大空闲时间为0,执行完毕即销毁,超出最大线程数进行等待。高并发下控制性能。
- 创建一个定长的线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool具有时间调度特性的线程池,必须初始化核心线程数,底层使用DelayWorkQueue实现延迟特性
- 创建一个可定期或者延时执行任务的定长线程池,支持定时及周期性任务执行
- newSingleThreadExecutor核心线程数与最大线程数为1,用于不需要并发顺序执行
- 创建一个单线程化线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定的顺序(FIFO,LIFO,优先级)执行
线程池的四个参数:
| core核心线程数 | max最大线程数 | timeout超时时间 | queue等待队列 | 适用场景 | |
|---|---|---|---|---|---|
| newCachedThreadPool | 初始值为0 | Integer最大数 | 60 | 不允许 | 高并发场景中,线程生命周期短的场景 |
| newFixedThreadPool | 固定初始值 | 固定初始值 | 0,执行完即销毁 | 允许 | 并发量稳定,每次执行固定的线程数 |
| newScheduledThreadPool | 固定初始值 | 不限 | 用完即作废 | 允许 | |
| newSingleThreadExecutor | 1 | 1 | 0 | 允许 | 不需要并发的场景 |
在什么条件下使用线程池
- 单个任务处理的时间比较短
-
使用线程池的好处:
减少在创建和销毁线程上所花的时间以及系统资源的开销
- 如不使用线程池,有可能造成系统创建大量线程而导致消耗完系统内存以及“过度切换”
工作原理:
为什么使用线程池?
诸如web服务器、数据库服务器、文件服务器、或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务。请求以某种方式到达服务器,这种方式可能是通过网络协议(例如HTTP、FTP、POP等)通过JMS队列或者可能通过轮询数据库。不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的。 构建服务器应用程序的一个过于简单的模型应该是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务。实际上,对于原型开发这种方法工作得很好,但如果试图部署以这种方式运行的服务器应用程序,那么这种方法的严重不足就很明显。每个请求对应一个线程(thread-per-request)方法的不足之一是:为每个请求创建一个新线程的开销很大;为每个请求创建新线程的服务器在创建和销毁线程上花费的时间和消耗的系统资源要比花在处理实际的用户请求的时间和资源更多。 除了创建和销毁线程的开销外,活动的线程也消耗系统资源。在一个JVM里创建太多的线程可能会导致系统由于过度消耗内存而用完内存或“切换过度”。为了尽量防止资源不足,服务器应用程序需要一些办法来限制任何给定时刻处理的请求数目。 线程池为线程生命周期开销问题和资源不足问题提供了解决方案。通过对多个任务重用线程,线程创建的开销被分摊到多个任务上。其好处就是,因为请求到达时线程已经存在,所以无意中也消除了线程创建所带来的延迟。这样,就可以立即为请求服务,使应用程序响应更快。而且,通过适当地调整线程池中地线程数目,也就是当请求地数目超过某个阈值时,就强制其它任何新到地请求一直等待,直到获得一个线程来处理为止,从而可以防止资源不足。
四种默认线程池源码初步分析
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));}public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());}public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}//创建一个定长线程池,支持定时及周期性任务执行。延迟执行public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
线程池任务执行流程
- 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
- 当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
- 当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务将会创建新线程执行任务
- 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
- 当线程池中超过corePoolSize线程,空闲时间到达keepAliveTime时,释放空闲线程
- 当设置allowCoreThreadTimeOut(true)时,改参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
使用场景详解
newCachedThreadPool:
- 底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;时间单位TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
- 通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可用线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定时间,则该线程会被销毁
- 适用:执行很多短期的异步任务
/*** 1.创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程<br>* 2.当任务数增加时,此线程池又可以智能的添加新线程来处理任务<br>* 3.此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小*/public static void cacheThreadPool() {ExecutorService cachedThreadPool = Executors.newCachedThreadPool();for (int i = 1; i <= 10; i++) {final int ii = i;try {Thread.sleep(ii * 1);} catch (InterruptedException e) {e.printStackTrace();}cachedThreadPool.execute(()->out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii));}}-----output------线程名称:pool-1-thread-1,执行1线程名称:pool-1-thread-1,执行2线程名称:pool-1-thread-1,执行3线程名称:pool-1-thread-1,执行4线程名称:pool-1-thread-1,执行5线程名称:pool-1-thread-1,执行6线程名称:pool-1-thread-1,执行7线程名称:pool-1-thread-1,执行8线程名称:pool-1-thread-1,执行9线程名称:pool-1-thread-1,执行10
newFixedThreadPool
- 底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量n,corePoolSize和maximumPoolSize均为n;keepAliveTime为0L;时间单位TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue
() 无界阻塞队列 - 通俗:创建可容纳固定数量线程的池子,每个线程的存活时间是无限的,当池子满了就不再添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期任务
/*** 1.创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小* 2.线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程* 3.因为线程池大小为3,每个任务输出index后sleep 2秒,所以每两秒打印3个数字,和线程名称*/public static void fixTheadPoolTest() {ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);for (int i = 0; i < 10; i++) {final int ii = i;fixedThreadPool.execute(() -> {out.println("线程名称:" + Thread.currentThread().getName() + ",执行" + ii);try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}});}}------output-------线程名称:pool-1-thread-3,执行2线程名称:pool-1-thread-1,执行0线程名称:pool-1-thread-2,执行3线程名称:pool-1-thread-3,执行4线程名称:pool-1-thread-1,执行5线程名称:pool-1-thread-2,执行6线程名称:pool-1-thread-3,执行7线程名称:pool-1-thread-1,执行8线程名称:pool-1-thread-3,执行9
newSingleThreadExecutor
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;时间单位TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue
() 无界阻塞队列 - 通俗:创建只有一个线程的线程池,当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
- 适用:按顺序执行任务的场景
```java
/*
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,
保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
*/
public static void singleTheadPoolTest() {
}ExecutorService pool = Executors.newSingleThreadExecutor();for (int i = 0; i < 10; i++) {final int ii = i;pool.execute(() -> out.println(Thread.currentThread().getName() + "=>" + ii));}
——-output———- 线程名称:pool-1-thread-1,执行0 线程名称:pool-1-thread-1,执行1 线程名称:pool-1-thread-1,执行2 线程名称:pool-1-thread-1,执行3 线程名称:pool-1-thread-1,执行4 线程名称:pool-1-thread-1,执行5 线程名称:pool-1-thread-1,执行6 线程名称:pool-1-thread-1,执行7 线程名称:pool-1-thread-1,执行8 线程名称:pool-1-thread-1,执行9
<a name="MtNHN"></a>### newScheduledThreadPool- 底层:创建ScheduledThreadPoolExecutor实例,该对象继承了ThreadPoolExecutor,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;时间单位TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列- 通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构- 适用:执行周期性任务```java/*** 创建一个定长线程池,支持定时及周期性任务执行。延迟执行*/public static void sceduleThreadPool() {ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);Runnable r1 = () -> out.println("线程名称:" + Thread.currentThread().getName() +",执行:3秒后执行");scheduledThreadPool.schedule(r1, 3, TimeUnit.SECONDS);Runnable r2 = () -> out.println("线程名称:" + Thread.currentThread().getName() +",执行:延迟2秒后每3秒执行一次");scheduledThreadPool.scheduleAtFixedRate(r2, 2, 3, TimeUnit.SECONDS);Runnable r3 = () -> out.println("线程名称:" + Thread.currentThread().getName() +",执行:普通任务");for (int i = 0; i < 5; i++) {scheduledThreadPool.execute(r3);}}----output------线程名称:pool-1-thread-1,执行:普通任务线程名称:pool-1-thread-5,执行:普通任务线程名称:pool-1-thread-4,执行:普通任务线程名称:pool-1-thread-3,执行:普通任务线程名称:pool-1-thread-2,执行:普通任务线程名称:pool-1-thread-1,执行:延迟2秒后每3秒执行一次线程名称:pool-1-thread-5,执行:3秒后执行线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次线程名称:pool-1-thread-4,执行:延迟2秒后每3秒执行一次
备注
- 一般如果线程池任务队列采用LinkedBlockingQueue队列的话,那么不会拒绝任何任务(因为其大小为Integer.MAX_VALUE),这种情况下,ThreadPoolExecutor最多仅会按照最小线程数corePoolSize来创建线程,也就是说线程池大小被忽略了。
- 如果线程池任务队列采用ArrayBlockingQueue队列,初始化设置了最大队列数。那么ThreadPoolExecutor的maximumPoolSize才会生效,那么ThreadPoolExecutor的maximumPoolSize才会生效会采用新的算法处理任务
- 例如假定线程池的最小线程数为4,最大为8,ArrayBlockingQueue最大为10。随着任务到达并放到队列中,线程池中最多运行4个线程(即核心线程数)直到队列完全填满,也就是说等待状态的任务小于等于10个,ThreadPoolExecutor也只会利用4个核心线程处理任务。
- 如果队列已满,而又有新任务进来,此时才会启动一个新的线程,这里不会因为队列已满而拒接该任务,相反会启动一个新线程。新线程会运行队列中的第一个任务,为新来的任务腾出空间。如果线程数已经等于最大线程数。任务队列也已经满了,则线程池会拒绝这个任务,默认拒绝策略时抛出异常。
- 这个算法背的理念是:该池大部分时间仅使用核心线程(4个),即使有适量的任务在队列中等待运行。这时线程池就可以用作节流阀。如果挤压的请求变得非常多,这时该池就会尝试运行更多的线程来清理;这时第二个节流阀—最大线程数就起作用了。
