上一篇讲述了ThreadPoolExecutor的执行过程,我们也能看出来一个很明显的问题:这个线程池的构造函数比较复杂,对于不十分理解其运作原理的程序员,自己构造它可能体现和想象中不一样的行为。比如阻塞队列放什么,corePoolSize怎么设置等等。
所以和Math这种工具类一样,并发包也提供了一种工具类:Executors。
首先这个工具类的作用就是:提供静态方法帮你构造不同的线程池。那么先分析一下它的设计模式:
1,静态工厂方法模式,静态方法帮你构造线程池。
2,外观模式,用一个简单的接口屏蔽了内部细节。(这么说有一点点牵强)
这个Executors提供的最主要的工厂有三种:
1 public static ExecutorService newFixedThreadPool(int nThreads) { 2 return new ThreadPoolExecutor(nThreads, nThreads, 3 0L, TimeUnit.MILLISECONDS, 4 new LinkedBlockingQueue<Runnable>()); 5 } 6 7 public static ExecutorService newSingleThreadExecutor() { 8 return new FinalizableDelegatedExecutorService 9 (new ThreadPoolExecutor(1, 1, 10 0L, TimeUnit.MILLISECONDS, 11 new LinkedBlockingQueue<Runnable>())); 12 } 13 14 public static ExecutorService newCachedThreadPool() { 15 return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 16 60L, TimeUnit.SECONDS, 17 new SynchronousQueue<Runnable>()); 18 }
第一种:大小固定的线程池,构造的时候必须告知其最大线程数量(无论如何也不超过这个数字),同时corePoolSize和maximumPoolSize都直接设置为这个最大线程数目参数。keepAliveTime则设置为0。(更本没有“短工”,也就谈不上短工的成活时间了)
重要的来了:为什么阻塞队列是一个无界队列(其实也不完全是无界:Integer.MaxValue)?这其实是设计很有意思的一个地方。我们之前已经说过线程池的运行逻辑。如果把阻塞队列设置为无线大,那么在实际线程数量达到corePoolSize之后,再来的线程都会阻塞在这个队列里面。从而保证:根本就没有“短工”。也就限制了线程池的总大小。
第二种:只持有一个线程的线程池,直接把corePoolSize和maximumPoolSize都设置为1,没啥说的
第三种:线程池是无界的。而阻塞队列是一个SynchronousQueue,这个队列的特点是不存放对象的阻塞队列。每一次take必须先于put,换句话说和别的先放再取的队列不一样,这个队列是先“预订”,等一“到货”,立马“交付”。每一个操作都是先做:然后立马阻塞,等别人对应的操作来取,实现几乎直接的交付。
逻辑变成了:因为核心池大小为0,没有长工。放进来的线程先试图进入SynchronousQueue,如果在此之前有worker做完了自己的工作,去SynchronousQueue拿线程(然后被阻塞),就可以实现直接交付给这个空闲的worker执行。如果失败(没有worker是空闲的),那么因为进入失败会直接新建一个worker,进入maximumPool开始执行。而那些执行完自己task都去SynchronousQueue等60秒,如果还有“订单”要来,就开工干活,否者keepAliveTime到被销毁。