您的位置:首页 > 编程语言 > Java开发

【Java】并发之线程池

2015-11-23 19:05 597 查看
先看一段我们熟悉的代码:

[java]
view plaincopy





for (int i = 0; i < 100; i++) {  
    Thread thread = new Thread(new OneTask(i));  
    thread.start();  
}  

其中,OneTask为Runnable接口的实现类。

思考一个问题:当要异步执行大量任务时,比如1000个任务,通过这种方法,就要创建1000个线程。大量线程的创建、销毁,并发执行与线程调度,是一笔很大的性能开销。另外,这么多线程难以很好地管理,有多少任务正在执行、多少任务已经执行完成,无法得知。

线程池,可以很好地处理这些问题。从Java中实现线程池的类为ThreadPoolExecutor。上面的代码,修改为:

[java]
view plaincopy





LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<Runnable>(90);  
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 20, TimeUnit.SECONDS, workQueue);  
for (int i = 0; i < 100; i++) {  
    threadPoolExecutor.execute(new OneTask(i));  
}  

ThreadPoolExecutor调用execute方法来“执行”任务(Runnable)实例,其实,说“提交”任务实例更为合理,因为不一定就能立即执行,只是提交到线程池,由线程池来调度执行。

来看一下ThreadPoolExecutor构造方法:

[java]
view plaincopy





public ThreadPoolExecutor(int corePoolSize,   
                    int maximumPoolSize,   
                    long keepAliveTime,   
                    TimeUnit unit,   
                    BlockingQueue<Runnable> workQueue);  

corePoolSize:线程池核心线程数大小,所谓“核心线程”,即在线程池中必不可少的,即使该线程负责执行的任务执行完毕,也不可少销毁自己,必须留在池中,以等待执行新的任务。

maximumPoolSize:线程池最大线程数大小,要注意,最大线程数总会大于或等于核心线程数。为什么会有大于?因为,除了核心线程外,线程池还可以创建一些新线程加入池中,这些新线程不同于核心线程,它们完成任务,空闲一定的时间后便会销毁掉。而这个时间,由下面两个参数决定。

keepAliveTime:非核心线程空闲时保留在池中多长时间,除非有新任务要求它去执行,否则过了这段时间,该线程会销毁掉。TimeUnit为时间单位类,如传参TimeUnit.SECONDS,表示keepAliveTime的单位为秒。

workQueue:一个BlockingQueue类型的参数,我们暂且简单地理解为一个队列。线程池使用这个队列来装载等待的任务。上面代码中,使用了BlockingQueue接口的一个实现类LinkedBlockingQueue,它的构造方法有一个参数capacity,决定了线程池等待队列的容量,即最多可以允许多少个任务加入等待队列。

下面分析下线程池创建、提交任务、任务等待、执行任务的过程。

a. 当ThreadPoolExecutor刚构造时,池中是还没有线程的。

b. 当调用execute()方法提交任务时,线程池会创建一个线程,来执行该任务,此时,线程池当前的线程数(pool size)为1。

c. 继续提交任务,继续创建线程(假设前面的任务都还没有这么快执行完),池中线程数递增1,依此类推。当达到corePoolSize个时,就不再创建新线程,新提交的任务就会进入等待队列,等到corePoolSize个线程中,有某个线程执行完任务时,等待队列中的任务才得以进入线程池中执行。

d.(继续假设前面的任务都还没有这么快执行完)继续提交任务,继续加入等待队列,当超出了等待队列所能容纳的最大限度(BlockingQueue的capacity值)时。前面说的maximumPoolSize参数要发挥作用了。当设置的maximumPoolSize值大于corePoolSize值,则此时线程池会创建新线程来执行该任务,线程池中线程数递增1。当线程池中的线程数达到了maximumPoolSize个,就不能再创建了,否则会报RejectedExecutionException异常。

e.线程执行完一个任务后,会从等待队列中取出新任务,然后执行新任务,这个过程,等待队列的任务数会减少。当线程池也渐渐执行完所有任务后,corePoolSize个的x线程(核心线程)继续存活不会销毁。而那些非核心线程(最多maximumPoolSize-corePoolSize个),过了keepAliveTime时间后便会被销毁。

Executors类是一工厂类,提供了一些创建常用ThreadPoolExecutor的方法。贴一下这些方法的源码,会更容易理解。

a. 创建固定个数的线程池执行器:

[java]
view plaincopy





public static ExecutorService newFixedThreadPool(int nThreads) {  
    return new ThreadPoolExecutor(nThreads, nThreads,  
                                  0L, TimeUnit.MILLISECONDS,  
                                  new LinkedBlockingQueue<Runnable>());  
}  

b. 创建单个线程的线程池执行器:

[java]
view plaincopy





public static ExecutorService newSingleThreadExecutor() {  
    return new FinalizableDelegatedExecutorService  
        (new ThreadPoolExecutor(1, 1,  
                                0L, TimeUnit.MILLISECONDS,  
                                new LinkedBlockingQueue<Runnable>()));  
}  

单线程执行器,可保证按顺序地执行任务。

c.创建缓存的线程池执行器:

[java]
view plaincopy





public static ExecutorService newCachedThreadPool() {  
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,  
                                  60L, TimeUnit.SECONDS,  
                                  new SynchronousQueue<Runnable>());  
}  

缓存线程执行器,执行大量的耗时短的任务,能很好地改善性能。

@容新华技术博客 - http://blog.csdn.net/rongxinhua -
原创文章,转载请注明出处
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: