通过多个线程并发自然能提高程序的性能,但无节制的创建线程,却会使程序的性能与稳定性大大的降低。首先,创建跟维持一个线程的生命周期都需要消耗资源。其次,一个系统能够创建的线程数受到系统资源的限制。对于一些网络程序,如果采用每请求每线程的话,当请求数达到一个上限时,系统性能会急剧恶化,甚至崩溃。通常解决这个问题的方法是采用线程池,虽然采用线程池不能从根本上解决高负载问题。
在一个线程池模型中,需要分清任务跟线程两种对象。用户提交的是任务,线程池用其里面的线程对该任务进行执行。那么就有两种不同的线程池模型:
1. 将任务放入阻塞队列,线程不停的从该队列中取任务执行。
2. 将空闲线程组成一个队列,每次任务过来时,从空闲队列中取一个空闲线程执行。任务执行完毕,将线程归还到空闲线程队列。
不过采用哪种模型,都会需要以下几个问题:
1. 任务很多,线程不够,是否创建更多的线程?
2. 任务太少,很多线程空着不用,是否回收?
3. 任务源源不断,线程已足够多,系统根本来不及处理,怎么办?
一般我们选择模型1加以实现。模型2实际上比模型1复杂的多。他不仅需要维护一个任务队列,还需要维护一个空闲线程队列,最麻烦的是当线程执行完任务后是让线程阻塞还是停止线程让其成为空闲线程,如果阻塞于任务队列实际上就是模型1.而停止线程然后又开启线程,别的不说,主线程需要承担更多的责任,增加程序复杂度。
一.任务队列
任务队列是个阻塞队列,使用的模式是生产者消费者模式。可选3种任务队列:
1、 无界队列。这个策略使用起来相当简单,但是负载过高时,可能OOM。
2、 有界队列。这自然需要考虑当队列满时,如何处理新来的任务。
3、 移交队列。这是个很有意思的队列。他缓存的是线程,他的数据一产生直接有线程取走。如果没有取走,必须得等取走后才能继续放入, 对应Java中的SynchronousQueue。
二.拒绝策略
当线程池已跟任务队列都满载的情况下,系统无法接受更多请求,则需要执行拒绝策略:
1、抛出异常,交给用户自己处理,也就是线程池不执行拒绝策略。Java的线程池这是个RuntimeException,需要显示捕获。
2、丢弃,就是把放弃一个请求,但是具体丢弃哪个请求则对应不同的丢弃策略。
3、调用者执行,就是让生产者线程执行,这样生产者线程就接受不到新的请求。这些请求会被放到TCP层的队列中,而系统继续过载,有TCP负责丢弃。实际上是转移责任,但却是个好办法。
三.增加与回收策略
有些线程池维持一个不变的线程数,而不管负载如何。有些线程池会根据负载的情况增加更回收线程,对于这类线程池就需要考虑它的增加跟回收策略。
这样线程池有两个初始的量来维持线程数目:
1. 核心线程数(core):如果线程的数目小于它,则直接创建线程来执行任务。
2. 最大线程数(max):线程的数量不能大于它。
(1)增加线程策略(cur表示当前线程数):
1. cur
2. core<=cur
3. core>=max,加任务放入队列,放入失败则执行拒绝策略
(2)回收线程策略(cur表示当前线程数):
1. 只有cur>core是才执行回收策略
2. 设回收的时间为keepalive, 则queue.poll(keepalive)返回null时,就可以把该线程回收。
阅读(928) | 评论(0) | 转发(0) |