ThreadPoolExecutor线程池参数设置技巧

本文详细解析了JDK1.5中ThreadPoolExecutor的内部机制,包括线程池参数设置、线程和任务处理流程、核心源码解读及线程池提供的外部方法。探讨了线程池在不同负载下的最优参数配置,以及如何根据系统需求选择合适的线程池类型。
摘要由CSDN通过智能技术生成

JDK1.5中引入了强大的concurrent包,其中最常用的莫过了线程池的实现ThreadPoolExecutor,它给我们带来了极大的方便,但同时,对于该线程池不恰当的设置也可能使其效率并不能达到预期的效果,甚至仅相当于或低于单线程的效率。

ThreadPoolExecutor类可设置的参数主要有:

  • corePoolSize

在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,(除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程)。

默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退出。

  • maxPoolSize
当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。
  • keepAliveTime

当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所有线程均会退出直到线程数量为0。

  • allowCoreThreadTimeout

是否允许核心线程空闲退出,默认值为false。

  • queueCapacity

任务队列容量。从maxPoolSize的描述上可以看出,任务队列的容量会影响到线程的变化,因此任务队列的长度也需要恰当的设置。

 

还有就是 workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

 

ArrayBlockingQueue;
LinkedBlockingQueue;
SynchronousQueue;
PriorityBlockingQueue 
ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
  • threadFactory:线程工厂,主要用来创建线程;
  • handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 
 

 

线程池按以下行为执行任务

  1. 当线程数小于核心线程数时,创建线程。
  2. 当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
  3. 当线程数大于等于核心线程数,且任务队列已满
    1. 若线程数小于最大线程数,创建线程
    2. 若线程数等于最大线程数,抛出异常,拒绝任务

 

系统负载

参数的设置跟系统的负载有直接的关系,下面为系统负载的相关参数:

  • tasks,每秒需要处理的最大任务数量
  • tasktime,处理第个任务所需要的时间
  • responsetime,系统允许任务最大的响应时间,比如每个任务的响应时间不得超过2秒。

 

参数设置

 

corePoolSize:

每个任务需要tasktime秒处理,则每个线程每钞可处理1/tasktime个任务。系统每秒有tasks个任务需要处理,则需要的线程数为:tasks/(1/tasktime),即tasks*tasktime个线程数。假设系统每秒任务数为100~1000,每个任务耗时0.1秒,则需要100*0.1至1000*0.1,即10~100个线程。那么corePoolSize应该设置为大于10,具体数字最好根据8020原则,即80%情况下系统每秒任务数,若系统80%的情况下第秒任务数小于200,最多时为1000,则corePoolSize可设置为20。

 

queueCapacity:

任务队列的长度要根据核心线程数,以及系统对任务响应时间的要求有关。队列长度可以设置为(corePoolSize/tasktime)*responsetime: (20/0.1)*2=400,即队列长度可设置为400。

队列长度设置过大,会导致任务响应时间过长,切忌以下写法:

LinkedBlockingQueue queue = new LinkedBlockingQueue();

这实际上是将队列长度设置为Integer.MAX_VALUE,将会导致线程数量永远为corePoolSize,再也不会增加,当任务数量陡增时,任务响应时间也将随之陡增。

 

maxPoolSize:

当系统负载达到最大值时,核心线程数已无法按时处理完所有任务,这时就需要增加线程。每秒200个任务需要20个线程,那么当每秒达到1000个任务时,则需要(1000-queueCapacity)*(20/200),即60个线程,可将maxPoolSize设置为60。

 

keepAliveTime:

线程数量只增加不减少也不行。当负载降低时,可减少线程数量,如果一个线程空闲时间达到keepAliveTiime,该线程就退出。默认情况下线程池最少会保持corePoolSize个线程。

 

allowCoreThreadTimeout:

默认情况下核心线程不会退出,可通过将该参数设置为true,让核心线程也退出。

 

以上关于线程数量的计算并没有考虑CPU的情况。若结合CPU的情况,比如,当线程数量达到50时,CPU达到100%,则将maxPoolSize设置为60也不合适,此时若系统负载长时间维持在每秒1000个任务,则超出线程池处理能力,应设法降低每个任务的处理时间(tasktime)。

 

 

转:https://blog.csdn.net/wojiaolinaaa/article/details/51345789

 

平常我们经常都会使用到线程池,但是有没考虑过为什么需要使用线程池呢?下面我列举一下问题,大家可以思考一下
1.当前服务器的硬件环境是多少核的CPU,它和线程的关系又是什么? 
2.jvm能创建多少个线程? 
3.多线程主要解决什么问题? 
4.你使用线程池的目的是什么? 

以上几个问题都是帮助你更好的使用java的线程(还可以衍生更多的小问题,如:jvm维护线程的消耗,cpu调度线程的消耗,应该使用多少个线程才能最大化利用多核CPU..)。答案需要自己去百度,我这也讲不好,反而误导大家。 

线程池顾名思义就是,存储了N多线程的一个池子,该池子维护了线程的创建、销毁,线程的创建数量,任务调度..
通常我们使用线程池,都是通过Executors 工厂方法来创建一个线程池。该工厂方法主要提供了以下几大类型线程池的创建:
1.CacheThreadPool 
这是一个线程数变动性非常强的线程池,默认配置下,它可以开启无限多个线程(Integer.maxSize 和 JVM允许线程数范围内)。且如果该线程池里的线程在60秒内如果是处于空闲状态(即没任务执行),那么该线程就会被回收,不再由线程池维护。如果有新任务进来时,由于之前的线程池里的线程已被回收,那么新的线程也会再次创建。当执行完任务,60秒内依旧无新任务的可执行话,那么该线程又会被再次回收。 

综合该线程池的特性,我们可以思考下什么情况下应该使用这类线程池。比如:我们的应用服务器上面,会在非固定时间(时间跨域度会尽可能大)和非固定的任务数量。 



2.FixedThreadPool
FixedThreadPool一个固定数量的线程池,且该线程池不会随着任务的变化而增多或减少线程数量。即该线程池下的线程池如果你不主动调用销毁shutdowm、purge之类的方法。那么这些线程将会永远被线程池维护着。

3.SingleThreadExecutor
SingleThreadExecutor是一个固定单线程的线程池,该线程池会永远都保持着一个线程的活动状态,如果该线程池的单线程因某些异常而退出后,线程池会继续创建一个新的线程。

4.ScheduledThreadPool
ScheduledThreadPool是一个支持任务定时调度的线程池。
 

以上四种线程池,除了ScheduledThreadPool,其他三种都是通过创建一个ThreadPoolExecutor对象来实现的,只是在构造该对象时候,初始化该内部的成员变量值不同,所以造就了其他三种类型的线程池的存在。下面看看ThreadPoolExecutor都有些什么成员变量,他们又分别有什么意义?
以下是ThreadPoolExecutor的构造方法之一,而Executors都是通过该构造方法来创建其他三种不同的线程池的。

 
  1. public ThreadPoolExecutor(int corePoolSize,

  2. int maximumPoolSize,

  3. long keepAliveTime,

  4. TimeUnit unit,

  5. BlockingQueue<Runnable> workQueue,

  6. ThreadFactory threadFactory) {

  7. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,

  8. threadFactory, defaultHandler);

  9. }

  10.  
  11. /**

  12. 在说这些参数之前,需要先了解下线程池内部的一些工作机制。

  13. 线程池ThreadPoolExecutor会自动调节线程池的大小(看ThreadPoolExecuroe.getPoolSize),依据corePoolSize参数的范围大小和maximumPoolSize。

  14. 当一个新任务通过ThreadExecutor.execute或者submit被提交时,

  15. 如果当前线程池中的线程数量小于所设定的corePoolSize,一个新的线程则会被创建来处理该请求,即使这些核心的线程池是空闲的(当前没有处理任何任务)。

  16. 如果当前线程池中的线程数量等于所设定的corePoolSize,那么新提交的任务则不会再创建新的线程,而是把该任务放进线程池所维护的任务队列workQueue中。当其他线程执行完了任务那么则会从任务队列中持续的获取任务来执行。

  17. 只有当前线程池中的数量超过了corePoolSize但是小于maximumPoolsize,且当queue满了才会去创建新线程(该创建的新线程必须小于maximumPoolsize)来处理该请求。

  18.  
  19. 以下是该构造方法的参数说明:

  20. corePoolSize : 线程池的核心线程数。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setCorePoolSize。

  21.  
  22. maximumPoolSize : 线程池的最大线程数,只有当任务队列满了才会创建新的线程。该参数并不是初始化时候写死了,线程池对象构造完成以后也能通过它提供的一些方法动态修改,ThreadPoolExecutor.setMaximumPoolSize。

  23.  
  24. keepAliveTime : 线程池所维护的线程的活动时间。如果超过了该时间范围,而线程还是空闲的,那么该线程将会被回收。不再由线程池所维护。以下是官方的一些说明:

  25. 如果当前线程池数大于核心线程池数,且这些线程是空闲的超过所设定的存活时间keepAliveTime,那么这些多出来的线程则会被终止,可以降低线程资源消耗。通过设置尽可能大的过期时间来阻止空闲线程被销毁,可手动调用setKeepAliveTime来设置。默认情况下,该策略只能适用于大于核心线程数的线程,但是可以通过设置ThreadPoolExecutor.allowCoreThreadTimeOut(boolean),则该策略也适用于核心线程数。

  26.  
  27. unit : 和上面keepAliveTime所对应,表示活动时间的时间单位。如 毫秒、秒、分..

  28.  
  29. workQueue : 初始化一组任务,但是该组任务并不会马上执行,需要手动调用prestartCoreThread或者prestartAllCoreThreads来预先启动线程才会执行这些初始化的任务。否则只有当你提交第一个任务时候他才会执行,且可能是单线程执行(取决于你提交几次任务)..

  30.  
  31. threadFactory : 传入一个线程工厂。通过该线程工厂可以创建线程。它创建的所有线程都是通过同样的ThreadGroup和同样的NORM_PRIORITY和non-daemon状态。通过提供的不同ThreadFactory,可以掌握修改线程名字,线程组,优先级,守护状态等。如果线程池调用线程工厂创建一个线程失败时,则返回一个null。且executor会继续,但是可能不会执行任何任务。

  32. 如果我们自己重写封装了一遍线程工厂,还有个好处就是可以通过该线程工厂实例维护所有由它创建的线程。

  33.  
  34. 上次我在群里看到一个群友说,他去面试,面试官问他如何判断线程池中一个指定线程当前是否在执行任务,你们觉得可以吗?

  35. **/


上面介绍完了线程池内部参数的意义,下面说下线程池还提供了一些什么外部方法供开发者使用?
Hook methods
提供了一些钩子方法如:ThreadPoolExecutor.beforeExecute and ThreadPoolExecutor.afterExecute。这些方法可在任务执行前后被执行。场景:重新初始化ThreadLocal。收集统计信息,添加日志记录。。

Queue maintenance
ThreadPoolExecutor.getQueue允许访问工作队列来完成监视和调试,但强烈不建议开发者使用它来完成其他目的。

上面说明了线程池的一些构造参数和外部方法,了解了这些后我们可以直接使用ThreadPoolExecutor来构建适合我们的线程池,而不再需要Executors。当然,通过Execturos创建的线程池再配合上面说的一些动态设置参数的方法,也能起到一些很好的效果。


 

准备开始解刨核心源码了!

先看看成员变量: 
参数runstate 
RUNNING:接收新任务和处理队列任务 
SHUTDOWN:不接收新任务,但处理队列里的任务 
STOP:不接收新任务,也不处理队列任务何中断正在执行的任务 
TERMINATED:和STOP一样 
以下是runstate状态的转换过程: 
RUNING->SHUTDOWN 调用shutdown() 
(RUNNING or SHUTDOWN) ->STOP 调用shutdownnow() 
SHUTDOWN->TERMINATED 当队列和线程池都是空的 
STOP->TERMINATED 当线程池是空的

workQueue: 
该成员变量是个阻塞队列,主要用于存储需要执行任务和把任务转交给worker线程

mainLock: 
用来对成员变量pooSize,corePoolSize,maximumPoolSize,runState、workers修改时候同步

termination: 
用来支持waitTemination

workers: 
集合包含的所有worker线程在线程池,只能通过mainLock访问

keepAliveTime: 
设置空闲线程等待多时毫秒被回收

allowCoreThreadTimeOut: 
设置核心线程空闲时候是否被回收,默认false

corePoolSize:核心线程数,需要使用mainLock同步更新

maximumPoolSize:最大线程数,需要使用mainLock同步更新

poolSize:当前线程数,需要使用mainLock同步更新

handler:当线程池饱和或者shutdown时候被执行

threadFactory: 线程工厂

largestPoolSize: 最大线程数,这个参数的存在似乎是为了更正并发情况下poolSize的修改

completedTaskCount:获取已完成的任务数量

defaultHandler:默认的拒绝处理器

addWorker失败的话可能会导致线程池关闭

通常我们把任务通过submit或者execute提交到线程池中,然后接着就什么都不用干,等待线程池帮我们执行任务了。那么线程池内部是如何执行这些任务的呢?它又是如何维护线程和任务的关系?
下面先看一幅关于线程池内部运作的图:

从图中可以看出Worker实现了Runnable接口且封装了线程(注意它在通过线程工厂创建线程时候把this,即Worker传入到Thread的构造参数)
在代码上是线程池维护了一组Worker,通过Worker.thread.start()后,那么Worker所封装的线程就会运行Worker自身的run实现。最后在run实现里持续的从workQueue中获取任务来执行,由于workQueue是阻塞队列,所以如果队列中没有任务,那么该线程则会被挂起等待唤醒(有任务过来),下面从源码中看看:

 
  1. /**

  2. Worker类里封装了Thread和任务Runnable,还有completedTasks。可以注意到创建一个Thread时候把this传入,这样的话如果我调用Worker.thread.start()就相当于该线程会执行Worker里的run方法了。completedTasks是用来记录该线程完成了多少个任务(非整个线程池)。

  3.  
  4. 注意该Worker继承了AQS同步基础器,它主要实现了互斥锁的功能,但是这个互斥锁和ReentrantLock有点不同,该实现是不允许线程重入获取锁的。下面说说为什么要实现锁功能和非重入:

  5. 1.lock方法主要用在标明当前线程正在执行任务中,而private interruptIdleWorkers 方法需要使用tryLock来判断当前线程是否正在执行任务,如果非执行任务状态则表明可能是正在获取任务,那么该线程属于空闲状态,可以被中断。

  6. 2.看回答1可以知道这通过ReentrantLock也能实现,但是如果我们在提交一个任务给线程池(实现一个Runnable),如果该

  7. 任务里面调用了和interruptIdleWorkers相关的,需要中断当前可获取锁(代表空闲)的线程。如果我们不使用非重入锁,这个任务线程就会给中断,从而导致一些奇怪的问题。

  8.  
  9. **/

  10. private final class Worker

  11. extends AbstractQueuedSynchronizer

  12. implements Runnable

  13. {

  14. /**

  15. * This class will never be serialized, but we provide a

  16. * serialVersionUID to suppress a javac warning.

  17. */

  18. private static final long serialVersionUID = 6138294804551838833L;

  19.  
  20. /** Thread this worker is running in. Null if factory fails. */

  21. final Thread thread;

  22. /** Initial task to run. Possibly null. */

  23. Runnable firstTask;

  24. /** Per-thread task counter */

  25. volatile long completedTasks;

  26.  
  27. /**

  28. * Creates with given first task and thread from ThreadFactory.

  29. * @param firstTask the first task (null if none)

  30. */

  31. Worker(Runnable firstTask) {

  32. setState(-1); // inhibit interrupts until runWorker

  33. this.firstTask = firstTask;

  34. this.thread = getThreadFactory().newThread(this);

  35. }

  36.  
  37. /**

  38. 主要看这里,其他的都是关于AQS的实现,具体的可以看AQS源码+我的AQS源码分析篇。

  39.  
  40. **/

  41. public void run() {

  42. runWorker(this);

  43. }

  44.  
  45. // Lock methods

  46. //

  47. // The value 0 represents the unlocked state.

  48. // The value 1 represents the locked state.

  49.  
  50. protected boolean isHeldExclusively() {

  51. return getState() != 0;

  52. }

  53.  
  54. protected boolean tryAcquire(int unused) {

  55. if (compareAndSetState(0, 1)) {

  56. setExclusiveOwnerThread(Thread.currentThread());

  57. return true;

  58. }

  59. return false;

  60. }

  61.  
  62. protected boolean tryRelease(int unused) {

  63. setExclusiveOwnerThread(null);

  64. setState(0);

  65. return true;

  66. }

  67.  
  68. public void lock() { acquire(1); }

  69. public boolean tryLock() { return tryAcquire(1); }

  70. public void unlock() { release(1); }

  71. public boolean isLocked() { return isHeldExclusively(); }

  72.  
  73. void interruptIfStarted() {

  74. Thread t;

  75. if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {

  76. try {

  77. t.interrupt();

  78. } catch (SecurityException ignore) {

  79. }

  80. }

  81. }

  82. }

看看Worker run的实现方法:

 
  1. final void runWorker(Worker w) {

  2. /**

  3. 这里获取当前执行线程,就是Worker所封装的Thread(因为是通过该Thread启动的,然后执行自身的run方法)

  4. **/

  5. Thread wt = Thread.currentThread();

  6. Runnable task = w.firstTask;

  7. w.firstTask = null;

  8. // 这个似乎没什么用的

  9. w.unlock(); // allow interrupts

  10. boolean completedAbruptly = true;

  11. try {

  12. //如果存在第一个任务则直接执行该任务,否则从任务队列里阻塞获取任务

  13. while (task != null || (task = getTask()) != null) {

  14. w.lock();

  15. //如果线程池状态已被标为停止,那么则不允许该线程继续执行任务!或者该线程已是中断状态,

  16. //也不允许执行任务,还需要中断该线程!

  17. if ((runStateAtLeast(ctl.get(), STOP) ||

  18. (Thread.interrupted() &&

  19. runStateAtLeast(ctl.get(), STOP))) &&

  20. !wt.isInterrupted())

  21. wt.interrupt();

  22. try {

  23. //执行任务前的钩子方法

  24. beforeExecute(wt, task);

  25. Throwable thrown = null;

  26. try {

  27. //真正的执行任务代码

  28. task.run();

  29. } catch (RuntimeException x) {

  30. thrown = x; throw x;

  31. } catch (Error x) {

  32. thrown = x; throw x;

  33. } catch (Throwable x) {

  34. thrown = x; throw new Error(x);

  35. } finally {

  36. //执行任务后的钩子方法

  37. afterExecute(task, thrown);

  38. }

  39. } finally {

  40. task = null;

  41. w.completedTasks++;

  42. w.unlock();

  43. }

  44. }

  45. completedAbruptly = false;

  46. } finally {

  47. //执行到这里代表该线程已被终止,将被回收(从线程池的workers里删除该线程)。

  48. //这个方法同时也代表了当线程超出了空闲时间后,将不再由线程池维护,而是被GC回收。具体可以看

  49. //getTask。由于getTask是以阻塞方式从阻塞队列获取任务,可以通过阻塞获取时候设定一个阻塞时间

  50. //来达到 keepAliveTime空闲功能。

  51. processWorkerExit(w, completedAbruptly);

  52. }

  53. }

  54.  

下面看看getTask是如何实现 线程池维护线程的keepAliveTime功能的。

 
  1. private Runnable getTask() {

  2. boolean timedOut = false; // Did the last poll() time out?

  3.  
  4. retry:

  5. for (;;) {

  6. int c = ctl.get();

  7. //获取当前线程池的状态(这部分具体最后讲)

  8. int rs = runStateOf(c);

  9.  
  10. // 如果线程池已被shutdown或者由于其他原因关闭,那么则终止该线程,返回null,最后就会走

  11. //processWorkerExit方法 了

  12. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {

  13. decrementWorkerCount();

  14. return null;

  15. }

  16.  
  17. boolean timed; // Are workers subject to culling?

  18.  
  19. for (;;) {

  20. //获取线程池当前的线程数(worker数量则代表线程数)

  21. int wc = workerCountOf(c);

  22. //判断是否需要采取设置 阻塞时间的方式获取任务.如果核心线程也需要空闲回收或者当前线程数

  23. //量已经超越了核心线程数,那么都需要采取阻塞时间获取任务方式。

  24. timed = allowCoreThreadTimeOut || wc > corePoolSize;

  25. //判断是否需要跳出循环,循环仅仅只是为了cas修改减少线程池的线程数。

  26. if (wc <= maximumPoolSize && ! (timedOut && timed))

  27. break;

  28. // 执行到这里代表阻塞获取任务超时,keepAlivetime时间到了。该线程将被回收

  29. if (compareAndDecrementWorkerCount(c))

  30. return null;

  31. c = ctl.get(); // Re-read ctl

  32. if (runStateOf(c) != rs)

  33. continue retry;

  34. // else CAS failed due to workerCount change; retry inner loop

  35. }

  36.  
  37. try {

  38. //如果需要采用阻塞形式获取,那么就poll设定阻塞时间,否则take无限期等待。

  39. Runnable r = timed ?

  40. workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :

  41. workQueue.take();

  42. if (r != null)

  43. return r;

  44. timedOut = true;

  45. } catch (InterruptedException retry) {

  46. timedOut = false;

  47. }

  48. }

  49. }

看了上面知道了线程池最核心的Worker是如何持续处理任务队列中的任务,和keepAlivetimed回收空闲线程。下面再看看execute一个任务后,线程池的处理步骤。比如核心线程数和最大线程数的体现,线程启动等

 
  1. public void execute(Runnable command) {

  2. if (command == null)

  3. throw new NullPointerException();

  4.  
  5. int c = ctl.get();

  6. //判断当前线程池的线程数是否少于核心线程数,只要少于核心线程数都会addWorker创建一个新Worker(新线程)

  7. //来处理新任务

  8. if (workerCountOf(c) < corePoolSize) {

  9. if (addWorker(command, true))

  10. return;

  11. c = ctl.get();

  12. }

  13. //当前线程数大于核心线程数或者addWroker失败,需要把任务提交到任务队列,等待Worker线程空闲后处理

  14. if (isRunning(c) && workQueue.offer(command)) {

  15. int recheck = ctl.get();

  16. //判断当前线程池状态是否正在运行(防止前面判断时候出现并发问题)

  17. if (! isRunning(recheck) && remove(command))

  18. reject(command);

  19. //如果当前线程池数量为0则创建新线程。

  20. else if (workerCountOf(recheck) == 0)

  21. addWorker(null, false);

  22. }

  23. //执行到这里代表当前线程已超越了核心线程且任务提交到任务队列失败。(可以注意这里的addWorker是false)

  24. //那么这里再次调用addWroker创建新线程(这时创建的线程是maximumPoolSize)。

  25. //如果还是提交任务失败则调用reject处理失败任务

  26. else if (!addWorker(command, false))

  27. reject(command);

  28.  
  29. }

  30.  
  31. private boolean addWorker(Runnable firstTask, boolean core) {

  32. retry:

  33. for (;;) {

  34. int c = ctl.get();

  35. int rs = runStateOf(c);

  36.  
  37. // Check if queue empty only if necessary.

  38. if (rs >= SHUTDOWN &&

  39. ! (rs == SHUTDOWN &&

  40. firstTask == null &&

  41. ! workQueue.isEmpty()))

  42. return false;

  43.  
  44. for (;;) {

  45. int wc = workerCountOf(c);

  46. //当前线程池的数量已经达到限定,不能添加新的线程了!

  47. if (wc >= CAPACITY ||

  48. wc >= (core ? corePoolSize : maximumPoolSize))

  49. return false;

  50. //cas修改增加线程池所拥有的线程数

  51. if (compareAndIncrementWorkerCount(c))

  52. break retry;

  53. c = ctl.get(); // Re-read ctl

  54. if (runStateOf(c) != rs)

  55. continue retry;

  56. // else CAS failed due to workerCount change; retry inner loop

  57. }

  58. }

  59.  
  60. boolean workerStarted = false;

  61. boolean workerAdded = false;

  62. Worker w = null;

  63. try {

  64. final ReentrantLock mainLock = this.mainLock;

  65. //创建一个新的Worker,则封装了一个firstTask

  66. w = new Worker(firstTask);

  67. final Thread t = w.thread;

  68. if (t != null) {

  69. mainLock.lock();

  70. try {

  71. // Recheck while holding lock.

  72. // Back out on ThreadFactory failure or if

  73. // shut down before lock acquired.

  74. int c = ctl.get();

  75. int rs = runStateOf(c);

  76.  
  77. if (rs < SHUTDOWN ||

  78. (rs == SHUTDOWN && firstTask == null)) {

  79. if (t.isAlive()) // precheck that t is startable

  80. throw new IllegalThreadStateException();

  81. //提交Worker到线程池所维护的workers集合中(可以认为这是一组线程)

  82. workers.add(w);

  83. int s = workers.size();

  84. if (s > largestPoolSize)

  85. largestPoolSize = s;

  86. workerAdded = true;

  87. }

  88. } finally {

  89. mainLock.unlock();

  90. }

  91. if (workerAdded) {

  92. //启动线程,执行Worker执行的run实现

  93. t.start();

  94. workerStarted = true;

  95. }

  96. }

  97. } finally {

  98. if (! workerStarted)

  99. addWorkerFailed(w);

  100. }

  101. return workerStarted;

  102. }

  103.  
  104.  

线程池关于 线程和任务之间这部分核心源码已经讲了,下面不再说源码,说说线程池还给我们提供了一些什么方法,平常比较少用到的。 
setRejectedExecutionHandler(RejectedExecutionHandler) ,主要是设定当任务提交或者处理失败的后继处理。我们可以通过自定义实现一个RejectedExecutionHandler,当任务提交失败时候可以重试,或者记录日志之类的。默认是AbortPolicy实现抛出异常.

allowCoreThreadTimeOut(boolean value) ,动态允许核心线程也被回收。CacheThreadPoolExecutor也实现类似功能,不过它是通过设定核心线程数为0来实现的。这样创建都所有线程都是maximumpoolsized(允许被回收)

getQueue() 用来查看当前有哪些任务正在队列中等待执行

purge() 对于Future类的任务,可以获取所有该类已为取消状态的任务

getPoolSize() 获取当前线程池中的线程数量

getActiveCount() 只获取当前线程池中正在执行任务的线程数量

getTaskCount() 获取线程池已完成(包括正在执行的任务)的任务数量

getCompletedTaskCount() 只获取已完成的任务数量,不包括正在执行的

invokeAny(Collection<. extends Callable> tasks) 
提交一组任务,只要任务集合中有任何一个任务完成,那么则返回该任务结果且中断其他正在执行中的任务。使用场景如:现在你有四个磁 盘,你要从这四个磁盘中搜索一个名叫 xx的文件。任何一个盘中搜索到,其他盘就不需要再搜索了。

invokeAll(Collection<. extends Callable> tasks) 执行这组任务, 全部任务都需完成.

最后就是关于该篇源码开头的ctl 值问题了,ctl里面的值包含了两类值,一个是当前的线程池运行状态,一个是线程池的当前线程数量。至于如何理解这里面的逻辑,需要有位运算基础,可以结合以下代码来观察他们的位运算关系

 
  1. public class X {

  2. private static final int COUNT_BITS = Integer.SIZE - 3;

  3. private static final int CAPACITY = (1 << COUNT_BITS) - 1;

  4.  
  5. // runState is stored in the high-order bits

  6. private static final int RUNNING = -1 << COUNT_BITS;

  7. private static final int SHUTDOWN = 0 << COUNT_BITS;

  8. private static final int STOP = 1 << COUNT_BITS;

  9. private static final int TIDYING = 2 << COUNT_BITS;

  10. private static final int TERMINATED = 3 << COUNT_BITS;

  11.  
  12. // Packing and unpacking ctl

  13. private static int runStateOf(int c) { return c & ~CAPACITY; }

  14. private static int workerCountOf(int c) { return c & CAPACITY; }

  15. private static int ctlOf(int rs, int wc) { return rs | wc; }

  16.  
  17.  
  18. public static void main(String[] args) {

  19. System.out.println("COUNT_BITS:"+COUNT_BITS+" "+Integer.toBinaryString(COUNT_BITS));

  20. System.out.println("CAPACITY:"+CAPACITY+" "+format(Integer.toBinaryString(CAPACITY)));

  21. System.out.println("RUNNING:"+RUNNING+" "+format(Integer.toBinaryString(RUNNING)));

  22. System.out.println("SHUTDOWN:"+SHUTDOWN+" "+format(Integer.toBinaryString(SHUTDOWN)));

  23. System.out.println("STOP:"+STOP+" "+format(Integer.toBinaryString(STOP)));

  24. System.out.println("TIDYING:"+TIDYING+" "+format(Integer.toBinaryString(TIDYING)));

  25. System.out.println("TERMINATED:"+TERMINATED+" "+format(Integer.toBinaryString(TERMINATED)));

  26. System.out.println(runStateOf(TIDYING));

  27. System.out.println(format(Integer.toBinaryString(~CAPACITY)));

  28. }

  29.  
  30. private static String format(String str){

  31. //if(str!=null) return str;

  32.  
  33. char[] cs = str.toCharArray();

  34. StringBuilder sb = new StringBuilder();

  35. int j =0;

  36. for(int i=(cs.length-1); i>=0 ; i--){

  37. sb.append(cs[j++]);

  38. if(i % 4 ==0){

  39. sb.append(" ");

  40. }

  41. }

  42. return sb.toString();

  43.  
  44. }

  45.  
  46. }

这里写图片描述

### 回答1: 线程池参数的合理配置取决于应用程序的负载和可用的计算资源。以下是一些常用的线程池参数配置建议: 1. 核心线程数:根据应用程序的负载和可用的计算资源,设置合适的核心线程数。通常情况下,核心线程数应该等于可用处理器的数量或稍微大一些。 2. 最大线程数:根据应用程序的负载和可用的计算资源,设置合适的最大线程数。最大线程数的设置应该根据应用程序的可用内存和CPU使用率进行调整。 3. 空闲线程存活时间:根据应用程序的负载和可用的计算资源,设置合适的空闲线程存活时间。如果任务数量不够多,可以适当降低空闲线程的存活时间,以减少资源占用。 4. 等待队列:根据应用程序的负载和可用的计算资源,选择合适的等待队列实现。如果任务数量比较大,可以选择使用 ArrayBlockingQueue 或者 LinkedBlockingQueue,如果任务数量比较小,可以选择使用 SynchronousQueue。 5. 线程工厂:根据需要自定义线程工厂,设置线程的名称、优先级等属性,以便于监控线程池的执行情况。 总之,线程池参数的合理配置需要根据具体的应用程序需求进行调整。在配置时,需要考虑应用程序的负载、可用的计算资源和线程池的性能等因素,以达到最优的线程池性能。 ### 回答2: 线程池参数配置需要根据实际场景和需求进行合理的选择。以下是一些常见的线程池参数配置建议: 1. 核心线程数(corePoolSize)的配置应根据系统的负载情况和并发任务的数量来确定。如果系统负载较重或并发任务较多,可以适当调高核心线程数,以确保有足够的线程处理任务,避免任务等待;如果负载较轻或并发任务较少,可以适当降低核心线程数,减少资源占用。 2. 最大线程数(maximumPoolSize)的配置应根据系统的资源情况和并发任务的特点来确定。最大线程数一般要大于核心线程数,以应对突发的任务请求。但过高的最大线程数可能导致资源占用过多,导致系统性能下降或者出现资源竞争问题。 3. 任务队列(workQueue)的选择需要根据任务的特点和对任务响应时间的要求来确定。如果任务量较小或者希望快速响应任务,可以选择使用SynchronousQueue,它会直接将任务交给线程处理,不会进行任务排队;如果任务量较大或者希望控制任务的处理速度,可以选择使用有界队列(如ArrayBlockingQueue或LinkedBlockingQueue),可以避免任务直接交给线程处理,使系统更稳定。 4. 空闲线程的存活时间(keepAliveTime)的配置需要根据任务的特点和系统资源的情况来确定。若任务处理时间较短且任务量持续较大,可以适当减少空闲线程的存活时间,避免资源浪费。若任务处理时间较长或任务量较小,可以适当增加空闲线程的存活时间,提高线程的复用率。 5. 拒绝策略(rejectedExecutionHandler)的选择需要根据业务需求和系统特点来确定。常见的拒绝策略有AbortPolicy(默认)、CallerRunsPolicy、DiscardPolicy和DiscardOldestPolicy。根据具体情况,可选择合适的拒绝策略,处理不能接受的任务。 总之,线程池参数配置的合理与否,需要充分考虑系统的负载情况、任务的特点和业务需求,通过调优参数来达到最佳的性能和效果。 ### 回答3: 线程池是一种用于管理和调度线程的机制,在并发编程中起到了重要的作用。但是线程池的性能和效果取决于参数的配置。下面是线程池参数配置的一些建议。 1. 核心线程数(corePoolSize):是线程池中的最小线程数,即使线程池没有任务执行,核心线程也会一直存在。根据系统的负载和处理的任务数来调整核心线程数,一般建议设置为CPU核心数的1至2倍。 2. 最大线程数(maximumPoolSize):该参数线程池中最多容纳的线程数。根据系统的负载和任务的处理能力来调整最大线程数,一般建议设置为核心线程数的2至4倍。 3. 阻塞队列(workQueue):用于存放等待执行的任务。不同类型的任务可选择不同的队列,如有界队列ArrayBlockingQueue和无界队列LinkedBlockingQueue。选择合适的队列大小或容量取决于系统的负载和任务的数量,一般建议使用有界队列,避免任务过多导致系统资源耗尽。 4. 线程存活时间(keepAliveTime):当线程池中的线程超过核心线程数时,多余的空闲线程会被回收,该参数设置了空闲线程的存活时间。根据任务的类型和执行耗时来调整存活时间,以避免频繁的线程创建和销毁。 5. 拒绝策略(rejectedExecutionHandler):当线程池无法处理新提交的任务时,按照预先定义好的拒绝策略进行处理。常见的策略有AbortPolicy(丢弃任务并抛出异常)、CallerRunsPolicy(由调用者线程处理任务)等。选择合适的拒绝策略依赖于任务的重要性和系统的容忍性。 综上所述,线程池参数的配置需要根据系统负载、任务数量和任务类型等因素进行调整。合理的配置可以提高系统的性能、响应速度和资源利用率,而不合理的配置可能会导致性能下降、资源浪费或系统崩溃。因此,在配置线程池参数时需综合考虑各种因素,根据实际情况进行调整和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值