三、FixedThreadPool线程池流程分析
1、看上文可知,FixedThreadPool与CachedThreadPool只是参数不同,其他的都类似,现在我们就来看看不同的参数是怎么样造成不同结果的。
先回顾一下该线程池的参数:corePoolSize为nThreads,maximumPoolSize为nThreads,BlockingQueue为LinkedBlockingQueue<Runnable>实例;
回到execute(),addIfUnderCorePoolSize(),addThread()等几个核心方法上:
public void execute(Runnable command) { if (command == null) throw new NullPointerException(); if (poolSize >= corePoolSize || !addIfUnderCorePoolSize(command)) { if (runState == RUNNING && workQueue.offer(command)) { if (runState != RUNNING || poolSize == 0) ensureQueuedTaskHandled(command); } else if (!addIfUnderMaximumPoolSize(command)) reject(command); // is shutdown or saturated } }
private boolean addIfUnderCorePoolSize(Runnable firstTask) { Thread t = null; final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { if (poolSize < corePoolSize && runState == RUNNING) t = addThread(firstTask); } finally { mainLock.unlock(); } if (t == null) return false; t.start(); return true; }
private Thread addThread(Runnable firstTask) { Worker w = new Worker(firstTask); Thread t = threadFactory.newThread(w); if (t != null) { w.thread = t; workers.add(w); int nt = ++poolSize; if (nt > largestPoolSize) largestPoolSize = nt; } return t; }
根据代码的流程可知:
1)、该线程池接收第一个任务对象时,poolSize为0,而corePoolSize为nThreads,poolSize >= corePoolSize此时为false,所以会进行第二个条件判断,即进入addIfUnderCorePoolSize()方法,直至调用addThread()方法,运行该流程的一个非常直接的结果是poolSize会自增1,实质的效果是线程池在扩容。注意此时整体条件(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))为false,扩容完成后,execute()方法就结束了
2)、若线程池继续接收线程对象,重复第一步执行,直到poolSize == corePoolSize时,由于判断的短路设计,将不再调用addIfUnderCorePoolSize(),此时线程池的容量为nThreads,已经不再扩容了。注意这时候整体条件(poolSize >= corePoolSize || !addIfUnderCorePoolSize(command))为true,将进入下一个if判断。
由此可知:“FixedThreadPool一开始创建了数量为nThreads的线程,后续也不创建新的线程对象”,就是这样实现的。
3)、当线程池继续接收线程对象时,由于整体条件返回为true,将进入“runState == RUNNING && workQueue.offer(command)”条件判断,现在主要是看workQueue.offer(command)的返回结果了,注意该线程池选用的BlockingQueue为LinkedBlockingQueue<Runnable>实例,这个特性非常重要,它返回的结果为true,跟CachedThreadPool选用的SynchronousQueue<Runnable>实例有很大差别。执行到这儿就意味着后来接收到的线程对象是通过LinkedBlockingQueue<Runnable>集合传递到Worker内部类的方法中去执行了,并且执行的顺序和插入的顺序一样,即FIFO(first in first out),按先后顺序执行。
关于Worker内部类的执行可以查阅上文。
2、CachedThreadPool和FixedThreadPool线程池Execute流程的区别
1)、CachedThreadPool:由于poolSize >= corePoolSize 永远为true,所以该线程池的addIfUnderCorePoolSize()永远都不会被执行(短路判断原理)。
2)、CachedThreadPool线程池的扩容和执行主要靠的是addIfUnderMaximumPoolSize()方法,边扩容边执行。
3)、当CachedThreadPool池中有线程任务完成,但还没有被回收时,新接收的线程任务执行到workQueue.offer()方法时,会返回true,然后直接唤醒线程池内的worker对象执行,这样就可以利用之前创建好的worker对象,少创建一个新的线程池对象,达到重复利用的目的,减缓池的扩张速度。若没有遇到这样的时机,还是会调用addIfUnderMaximumPoolSize()方法。
4)、FixedThreadPool线程池初始化时poolSize为0,corePoolSize为nThreads,刚开始执行时会调用addIfUnderCorePoolSize()方法,当线程池扩张到容量为nThreads时,就停止扩张,改由LinkedBlockingQueue存储新接收的线程对象,交予worker对象执行。
5)、回顾一下Worker内部类的getTask()方法,注意第二个if判断,可以发现,CachedThreadPool走的是r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS);这行代码,而FixedThreadPool则走r = workQueue.take();,workQueue的poll()方法和take()方法是有区别的,说简单点就是poll有时间限制,超时不候,结果会导致该线程对象被回收,而take()则能够等到天荒地老,该线程就一直不会被回收。所以为什么FixedThreadPool创建的线程池对象不会被回收,就是这里的原因。
Runnable getTask() { for (;;) { try { int state = runState; if (state > SHUTDOWN) return null; Runnable r; if (state == SHUTDOWN) // Help drain queue r = workQueue.poll(); else if (poolSize > corePoolSize || allowCoreThreadTimeOut) r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS); else r = workQueue.take(); if (r != null) return r; if (workerCanExit()) { if (runState >= SHUTDOWN) // Wake up others interruptIdleWorkers(); return null; } // Else retry } catch (InterruptedException ie) { // On interruption, re-check runState } } }
四、SingleThreadExecutor线程池
虽然他有个装饰类FinalizableDelegatedExecutorService,说白了只是包装了nThreads为1的FixedThreadPool,流程与上面相同,略过。