分类: Java
2014-12-09 09:44:57
另外线程属于稀缺资源,不能无限制的创建,通过线程池可以进行统一的创建和监控。 遇到了性能瓶颈,吞吐量上不去的时候,并不是一味的加大线程数就能提高性能,线程达到一定的程度,上下文切换会消耗很大的CPU,并且可能是在做无用功,所以线程数量要有一个度,凡事都应该有个度,常言道"过犹不及"。要找到最佳的线程数,需要根据应用的类型,比如CPU密集型还是IO密集型,CPU的个数,IO等待和执行的比率,以及程序串行化的比率等因素综合考虑,并通过性能测试来具体验证才能获得最优配置,毕竟实践是检验真理的唯一标准嘛,废话少说,抓紧时间看看线程池吧。
2.类图
这里最关键的是ThreadPoolExecutor类,隐藏了内部类和方法,不然太多太乱了,这样比较简洁,简洁是王道,简洁而非简陋。Executors提供了几个推荐使用的工厂方法。BlockingQueue用于存放暂时不能执行的线程队列。下面分别来看看这个关键类。
3. ThreadPoolExecutor的构造方法
4.Executors的几个工厂方法
5.拒绝策略
代码示例:
corePoolSize:即使他们(线程)是空闲的,仍然保存在池中的线程数。
maximumPoolSize:线程池允许的最大线程数。
keepAliveTime:如果线程数大于corePoolSize,如果线程超过这个时间,将会被干掉。
unit:跟keepAliveTime对应的时间单位。
workQueue:该队列用户存放未被执行的任务。仅仅存放通过execute方法提交的任务。
threadFactory:用于创建新线程的工厂。
handler:如果线程不能被执行的拒绝策略。
其他的都容易理解,corePoolSize 比较容易引起误解,下面解释一下corePoolSize和线程池的处理流程。
corePoolSize:同其他的线程池不同,线程池并不是一上来就创建corePoolSize的线程数等待执行,一开始是空的线程池,提交一个任务到线程池中才会创建一个线程来执行。在线程数未达到corePoolSize之前,即使池中有空闲的线程,也仍然会创建一个新的线程来执行。
corePoolSize的线程不会被回收。
作者建议使用Executors的方法来创建ThreadPoolExecutor对象。
其中corePoolSize和maximumPoolSize相等,LinkedBlockingQueue是一个无界的workQueue。
由于是个无界的queue,所以如果线程执行的时间很长,并且不停的向其提交新的任务的话,会一直放入queue,会导致系统垮掉。
通常为了保障系统的高可用,要限制资源的使用,其中就包括线程资源,所以建议使用有界队列,这样虽然某些请求会失败,但是最起码可以保障整个系统的可用性。
ThreadPoolExecutor中有几个实现RejectedExecutionHandler的内部类:
AbortPolicy: 这个比较暴力,直接抛异常,拒绝执行。
DiscardPolicy:这个是个空实现,直接丢弃。
DiscardOldestPolicy:丢弃队列中的,执行新来的。喜新厌旧啊这个。
CallerRunsPolicy: 使用调用者线程执行任务。
6.BlockingQueue
ArrayBlockingQueue:基于数组结构的阻塞队列。
LinkedBlockingQueue:基于链表结构的阻塞队列。 Executors.newFiexedThreadPool()使用的就是这个队列。
SynchronousQueue:不存储元素的阻塞队列,每个插入操作必须等到另一个线程的移除操作。Executors.newCachedThreadPool()就是使用这个队列。也就是说如果此时有空闲线程则转交给空闲线程处理,如果没有空闲线程则会增加新的线程来处理。
7.线程池的关闭
线程池的状态:
RUNNING: 接受新任务,执行队列中的任务。
SHUTDOWN: 不接受新任务,但是执行队列中的任务。RUNNING->SHUTDOWN,调用shutdown()方法。
STOP:不接受新任务,不执行队列中的任务,并终止正在执行的任务。RUNNING或者SHUTDOWN->STOP,调用shutdownNow()方法。
TERMIANTED:同STOP,但是所有线程已经terminated。
线程池一定要记得shutdown(),这就跟创建了数据库资源finally中记得关闭一样,如Connection ResultSet PreparedStatement Statement,还有文件,网络socket对象,创建后都要记得关闭。