你要想使用Thread pool你就必须得做两件事:
第一:创建用于存放Runnable类型实例(任务)的任务队列Queue<Runnable> workQueue 它通常用BlockingQueue<Runnable> workQueue来实现!
第二:创建pool本身:即一个用于存放Thread类型实例(线程)的一个集合 Collection<Thread> pool.
__________________________________________________________________________________________________
线程池在JAVA语言中的一个成熟的封装实现类是java.util.concurrent.ThreadPoolExecutor这个类,它实现了ExecutorService这个接口(是个线程池理念中的成熟接口),它是ExecutorService这个接口的一个成熟的实现类.
定义如下:
package java.util.concurrent;
public class ThreadPoolExecutor implements ExecutorService {
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler);
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler);
public boolean prestartCoreThread( );
public int prestartAllCoreThreads( );
public void setMaximumPoolSize(int maximumPoolSize);
public int getMaximumPoolSize( );
public void setCorePoolSize(int corePoolSize);
public int getCorePoolSize( );
public int getPoolSize( );
public int getLargestPoolSize( );
public int getActiveCount( );
public BlockingQueue<Runnable> getQueue( );
public long getTaskCount( );
public long getCompletedTaskCount( );
}
这个类的使用说明:
corePoolSize:指定线程池thread pool中 最精瑞线程(thread)数量,最核心的线程数量,应保持最少的线程(Thread类型实例对象)数量,核心处理业务线程数(应必须(满足)达到的最基本的水平)
maximumPoolSize:指定线程池thread pool中 可保持的最多线程(thread)数量,最大线程数量,应保持最满时的线程(Thread类型实例对象)数量(最大极限)
以上的这两个参数会使得线程池中现有的Thread类型实例化对象的数目会在[corePoolSize maximumPoolSize]区间内变化.
若在ThreadPoolExecutor 的构造函数中指定corePoolSize的取值和maximumPoolSize的取值相同,则该线程池(这里指ThreadPoolExecutor 类型实例对象)的pool中的线程数量始终会保持不变,且就是核心处理业务线程数!
若如果对corePoolSize的取值和maximumPoolSize的取值不相同,那么线程池(这里指ThreadPoolExecutor 类型实例对象)会动态调整期内部pool中的线程(Thread类型实例)的数量,使得该数量值落在corePoolSize的取值和maximumPoolSize的取值的区间之内.注意:线程池中的目前(当前)的线程数量用ThreadPoolExecutor 类型实例对象的getPoolSize()这个方法可以返回得到!
关于BlockingQueue<Runnable> workQueue,它是用来存放正在等待需要寄生到线程池中的线程(thread)上的Runnable类型实例(任务)的一个数据结构(队列),因此它workQueue叫做“任务队列”或“工作队列”注意:一旦一个工作队列BlockingQueue<Runnable> workQueue被一个线程池所使用,(传给了一个线程池(本例中是一个ThreadPoolExecutor 类型实例对象))那么这个 BlockingQueue<Runnable> workQueue就应该完全由线程池自行进行管理,你不要手动的管理它,应该让它完全托管!(无人值守地由线程池自动管理),你不要企图自己手动地显式调用有关处理该workQueue的workQueue自己的任何成员方法,即不要直接发送消息给workQueue,特别是往里面添加数据元素,若你要往这个任务队列中添加任务(Runnable类型实例)你也只能(官方推荐)使用线程池(ThreadPoolExecutor实例)的execute(Runnable xxx);方法来添加任务!(这样使用不犯毛病),切记你千万不要直接地运行(显式调用)workQueue自己的method,不然使用了该workQueue的线程池(ThreadPoolExecutor实例)内部的调度运作会放生混乱!
___________________________________________________________________________________________________________________________________________________
那么上文提到的workQueue(存放任务Runnable实例的地方)和线程池(这里指ThreadPoolExecutor类型实例)中的pool(存放thread的地方)是如何交互的呢?pool中的thread又是怎样接到分派给它的Runnable类型实例(任务)的呢?下文回答这两个问题!
首先你要明确线程池(ThreadPoolExecutor实例)的构造是用指定的corePoolSize=min与 maximumPoolSize=max这两个构造参数构造出来的,min指定了pool中最少要保持的线程thread的数量,max指定了pool中最多应保持的thread数量,当一个Runnable类型实例(任务)要交由线程池去处理(希望委托给线程池中pool区域内的thread(Thread类型对象)去处理)时(通过ThreadPoolExecutor实例的execute(Runnable xxx)方法来实现这一步骤),会发生以下五种情况中的一种。
第一:
如果pool中目前所创建的线程数量<min时,它就会在创建一个新的Thread类型实例来接收新分派给它的Runnable类型实例(任务),并运行该任务。即使现在Pool中仍有闲置中的线程对象,pool还是会尝试创建新的thread以努力达到线程池在初始化时所要求的要达到的核心业务处理线程数要达到min值的这个最低标准!
第二:如果pool中目前所创建的线程数量为n且min<n<max时,且此时pool中有闲置的thread时,那么pool就会用闲置的thread去接受新分派而来的Runnable类型实例(任务).
第三:如果pool中目前所创建的线程数量为n且min<n<max时,且此时pool中没有闲置的thread,pool中所有的thread都已分配到了任务(Runnable类型实例),且都在执行任务中(都在忙),此时pool会去检测workQueue(任务队列)的状态情况,若新到来的未被分配到thread上的Runnable类型实例能够放进该队列workQueue中(能够入队列),那么pool则不会再新为该可以排队等待的Runnable类型实例创建一个新的Thread类型对象,而是让该Runnable类型实例(任务)在workQueue工作队列中排队(先进的先出)等待,直到pool中的某个thread完成了它手里的任务(活)后又返回到pool的可调度区中,继而又变成了闲置的线程的时候,pool才会把该闲置的thread分派(调度)给这个Runnable类型实例(注意:这种情况下这个Runnable类型实例对象也必须具备出队列的资格才行,否则本着队列中的数据元素先进先出FIFO的原则,只有最早入工作队列的那个任务才会有优先获得pool分派给它闲置thread(闲置线程)的权利).
第四:如果pool中目前所创建的线程数量为n且min<n<max时,且此时pool中没有闲置的thread,pool中所有的thread都已分配到了任务(Runnable类型实例),且都在执行任务中(都在忙),此时pool会去检测workQueue(任务队列)的状态情况,若新到来的未被分配到thread上的Runnable类型实例不能够放进该队列workQueue中(不能够入任务队列,队列已满不可进入),但是线程池中的线程数量n仍然<max,那么pool则还会再新为该新来的Runnable类型实例创建一个新的Thread类型对象(这是因为n还没到max取值,n还可以继续接近设置的pool中线程的最大极限数目值max),虽然不会让这个Runnable类型实例对象进入工作队列(因为工作队列(任务队列)已满,它进不去了),但是pool仍然新创建线程thread并及时分派给该任务(Runnable类型对象),并试图使n值(线程数量)向max值靠近。
第五:如果pool中目前所创建的线程数量为n且min<n=max时,且此时pool中没有闲置的thread,pool中所有的thread都已分配到了任务(Runnable类型实例),且都在执行任务中(都在忙),此时pool会去检测workQueue(任务队列)的状态情况,若新到来的未被分配到thread上的Runnable类型实例不能够放进该队列workQueue中(不能够入队列,队列已满不可进入),那么pool则既不会再新为该Runnable类型实例创建一个新的Thread类型对象(这是因为n已经为max取值,设置的pool中线程的最大极限数目),更不会让这个Runnable类型实例对象进入工作队列(因为工作队列(任务队列)已满),而是将它抛弃!即将该任务(Runnable类型对象)抛弃!
总结:
当你在线程池机制中,你设置的任务队列(工作队列)BlockingQueue<Runnable> workQueue的容量大小为0时,这意味着相当于没有任务队列(工作队列)这么个东西,或者说这种设定下任务队列这个东西起不到任何作用了,它就是个摆设了,没有意义了,那么就会出现没有"等待中的任务排队"这个"排队"的现象,这种形势下:对于要分配给线程池处理的任务(Runnable类型实例)而言,它要么会被线程池立即接受并运行(执行任务)(即当pool中有闲置的空闲thread时会立即分配给该新到来的任务,并让其寄生执行),要么该新到来的任务(Runnable类型实例)会立即遭到线程池的拒绝处理将它抛弃,(这是因为第一:pool中没有闲置的thread可调度且n==max,因为所有的thread都在忙都在执行其他任务没空处理新任务,即pool中所有线程都是忙状态,而且n已经到达了max值,pool不可能再为新到来的Runnable实例创建新的线程thread了,第二:在上述情况下又没有了任务队列这个东西可用(即任务队列名存实亡),那么新到来的暂时不可马上被分配到thread的任务就无地可去了,被逼到了死路,因为没有了任务队列它就不能够排队等待了),因此这个任务Runnable实例就没有地方生存了,所以它只能自生自灭!(如果你为了能使Runnable类型实例任务无条件得以运行(不被拒绝),那么你就会想到企图将pool中的线程数量的最大极限值不加设定即max值趋于无穷大,这种情况下,你为了防止任务被拒绝新建的Thread类型对象数量会不断攀升以致爆膨,由于创建大量的线程使得系统维护它们的开销巨大,这就使得本应有的线程池性能特性之一(控制线程的无节制创建从而节省系统资源)这一特征荡然无存!),这就跟你起初想使用线程池帮助你优化系统的理念背道而驰了。
其次:当你设置的任务队列(工作队列)BlockingQueue<Runnable> workQueue的容量大小为不加限制的时候(容量无极限),那么有会出现什么现象呢?
答案:当任务队列的容量不加限制,会导致Runnable类型对象加入到workQueue中一定会成功,使得处理不过来的任务放到任务队列中去排队一定会成功,当待处理的任务爆膨的时候(这里指当pool中线程全忙没有空闲的闲置thread时),这些处理不过来的任务会排队(线程池中pool的线程全忙时新到来的没有被分配到线程的Runnable类型实例就会排队即被放到等待队列当中,当新的任务(Runnable型实例)越多时,排的队就越长,(要放到workQueue中的任务就要越多),这代表了pool永远不会创建比min还多的线程(因为处理不过来的任务会自动地排队,你就排去吧,没人搭理你,队伍排的长长的,而且pool不会单独创建新的线程去处理那些排不上队的Runnable类型任务,因为这种情况下根本不存在排不上队的任务了.篇外话:线程池的设计中pool只会为排不上队即放不进workQueue中去的Runnable类型实例任务创建新的线程Thread,以使得n值向max值趋近!这里的n指的是pool中的当前线程总数,max指的是初始化线程池时指定的线程池pool中限定的最多(大)线程总数,最大容量)(换句话说在workQueue的容量不做限定的这种情况下根本不存在排不了队的任务,即任务若要被放进任务队列中则一定会成功,因为任务队列容量无上限有的是地方,队伍排得越长,后续的排队的任务就越难被执行,因为前面排队的人实在太多了(且后排在后面的任务其优先被执行权不够(就越靠后)(这是由队列的FIFO的特性所决定的),就像排队搓澡一样,前面排的人越多,后面要搓澡的人等的时间就得越长)).
__________________________________________________________________________________________________________________________________
经验之谈:使用线程池的最佳策略:如下
使用线程池时,在初始化阶段要设定BlockingQueue<Runnable> workQueue(任务排队队列)的容量大小使其为一个有限的固定值,假设此值为P(即工作队列的最大容量值,即workQueue中所能够存放Runnable类型对象的最多数目要设为一个固定值p),其次应让pool中的总线程数量设定在一个闭区间内,即core(核心线程数量)或者说是应保持的最小线程数量min,与最大线程数量(pool中最多允许保存的thread数量)max,使得min<=n<=max的条件始终成立即可.这种情况下当有任务(Runnable类型实例)要委托各线程池的pool来调度thread来处理它时,pool首先会选择的算法策略是不断创建新的thread直到pool中当前有的线程总数n==min值为止,此时若还有到来的新的未处理的Runnable类型任务时,这些未得到处理的任务就要选择进人workQueue(任务队列),到任务队列中去排队,直到在任务队列中排队的任务(Runnable类型对象)的数量达到了p值为止(workQueue容量,超过这个值后,新来的Runnable类型任务就不能够再入队列了,因为workQueue已经满了,放不下去了),若继续还有新到来的Runnable类型对象(任务)到来,那么后来的这些排不了队的任务(放进workQueue中的任务)就会仍然被pool所接受,只不过pool要为这些排不了队的额外任务,再创建一批新的thread(Thread类型对象)(即新的线程),来处理接受这些新的额外的任务,直到n==max为止,pool不可能在创建新的thread了.(pool已到达能够创建的线程数量上限),若在n==max的情况下,还有新的更多的Runnable类型任务需要交付给线程池来处理时,线程池将拒绝处理这些过分多余的任务,使得这些最后多出来处理不了的任务被丢弃.让其自生自灭.
注意:使用固定容量值的workQueue+固定容量的pool这一策略的线程池的好处是:如果要执行的任务数比可分配与可调度的闲置thread数目要大的时候(即任务到来比处理的速度要快时),“过多的处理不过来的任务”就要进workQueue排队等待(按照fifo原则排队等待pool调度稍后有可闲置状态的thread去接受并处理它),当pool中的线程数量已到达上限max,且workQueue中的Runnable类型任务数量已到达p时,且pool中再无闲置thread(所有的thread全忙)时,那么继续到来的任务就能被拒绝处理,被抛弃,这样可以减少程序的负担,即减少线程池的可处理负担,提高了多线程程序的吞吐量。切记:没有魔术(梦之规则)(黄金规则)规则可以用来决定最佳容量大小的workQueue与pool容量的规则取值,可以遵循的道理与原则是:使用与CPU数量相同的thread数目,一般不会犯毛病,对于高要求与更复杂的多线程并发程序的设计,挑选pool容量值(线程池中线程数量的设置)的大小要由测试不同的值来确定线程池工作地最佳性能配置值!
_______________________________________________________________________________________________
本章总结:
在这一章中我们先探索讨论executor接口,它将多线程程序设计的细节对开发者隐藏了起来,它可以用来处理Runnable类型的任务,它是处理多Runnable类型对象(任务)的理想工具.
executor接口所提供的模型,能够让你所对于要处理的多个任务,描述并规理成串行化的多个任务的概念.使得你对多任务的串行化处理有了个理想的实现方案与工具.程序员可以只关注程序的逻辑需求,而不用烦恼于关于java线程(thread)如何被创建与使用(调度)的诸多细节.就能写出高效有用的多线程并发程序(或者说多任务处理程序).
java.util.concurrent.ThreadPoolExecutor这个类是对executor接口的良好实现,使用这个工具类可以在实现“线程池”效应(简化多线程程序的设计,加强程序的吞吐量使得多个任务被串行化并发处理,这里的串行化指得是任务等待处理是采用"排队等待的策略“,排队等待处理模式:遵循着FIFO原则排队)的好处外,可用其内的pool这个机制动态调节"池"中的线程数量,从而减少多线程对CPU的竞争,并能够让CPU密集的多线程程序更快地完成个别任务(Runnable类型任务).
java线程池被认为被使用的最重要的原因是:重复利用已经被创建出来的thread要比不断地新创建thread在效率与资源开销上都要好得多!另一个原因是:你会发现其实线程池的好处关键在于:当CPU资源竞争较少的时候(比如说需要并发执行的thread数量较少时),每个需要处理的任务(Runnable类型实例的run()方法)完成的平均时间要比其它方式(非线程池方式这里指的是盲目地创建多个独立的处理任务用的线程的方式)要少!
有效地使用java线程池的关键在于选择正确地pool容量(pool中可持有的thread总数),与workQueue实现所使用的Queue<Runnable>模型(Queue的模型有多种比如说:LinkedBlockingQueue,ArrayBlockingQueue等,还有就是限定容量的Queue与不限定容量的Queue等)你要选用哪种Queue模型完全是要根据你的应用需求,比如说未限定容量的Queue<Runnable>不会拒绝任务的入队列操作,它会使得任务(任务请求)得到累积的效果,而限定了容量的Queue<Runnable>模型当队列已满时,会导致新的更多的待处理任务遭到入队列拒绝,从而间接导致这样的任务遭到线程池拒绝处理并抛弃!要想用好java线程池你的花些功夫,但是它的回报是:简化多线程并发程序的逻辑设计与创建实现,增加了多任务并发处理程序的吞吐量.
——————————————————————————————————————
使用的案例代码如下:
package javathreads.examples.ch10.example1;
import java.util.concurrent.*;
import javathreads.examples.ch10.*;
public class ThreadPoolTest {
public static void main(String[] args) {
int nTasks = Integer.parseInt(args[0]);
long n = Long.parseLong(args[1]);
int tpSize = Integer.parseInt(args[2]);
ThreadPoolExecutor tpe = new ThreadPoolExecutor(
tpSize, tpSize, 50000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>( ));
Task[] tasks = new Task[nTasks];
for (int i = 0; i < nTasks; i++) {
tasks[i] = new Task(n, "Task " + i);
tpe.execute(tasks[i]);
}
tpe.shutdown( );
}
}
In this example, we're using the tasks to calculate Fibonacci numbers as we do in Chapter 9. Once the pool is constructed, we simply add the tasks to it (using the execute() method). When we're done, we gracefully shut down the pool; the existing tasks run to completion, and then all the existing threads exit. As you can see, using the thread pool is quite simple, but the behavior of the pool can be complex depending on the arguments used to construct it. We'll look into that in the next section.
note that task is instance of Task,The class Task in this example is one of the implementations for Runnable (Runnable is a interface of java)
________________________________________________________________________________________________________________________________________________