Java并发编程之线程池的理解与使用

时间:2022-03-24 18:03:40

         首先说说线程池的作用:一言以蔽之,就是提高系统效率。如果服务器对每个请求都分别创建一个线程的话,在很短时间内就会产生很多创建和销毁的动作,然而服务器在创建和销毁线程上花费的时间和消耗的系统资源都相当大。线程池就可以尽量减少这种情况的发生。

讲到线程池,不得不说的就是ThreadPoolExecutor这个类了。

在ThreadPoolExecutor类中提供了四个构造方法:

 public class ThreadPoolExecutor extends AbstractExecutorService {
......
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//队列已满,而且当前线程数已经超过最大线程数时的异常处理策略)
}

corePoolSize:核心线程数,核心线程会一直存活,即使没有任务需要处理。当线程数小于核心线程数时,即使现有的线程空闲,线程池也会优先创建新   线程来处理任务,而不是直接交给现有的线程处理。核心线程在allowCoreThreadTimeout被设置为true时会超时退出,默认情况下不会退   出。

maxPoolSize:当线程数大于或等于核心线程,且任务队列已满时,线程池会创建新的线程,直到线程数量达到maxPoolSize。如果线程数已等于     maxPoolSize,且任务队列已满,则已超出线程池的处理能力,线程池会拒绝处理任务而抛出异常。

keepAliveTime: 当线程空闲时间达到keepAliveTime,该线程会退出,直到线程数量等于corePoolSize。如果allowCoreThreadTimeout设置为true,则所             有线程均会退出直到线程数量为0。

unit:参数keepAliveTime的时间单位,有7种取值,从天到纳秒。

workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列分三种,分别是:ArrayBlockingQueue;LinkedBlockingQueue;SynchronousQueue;

threadFactory:线程工厂,主要用来创建线程;

handler:表示当拒绝处理任务时的策略,有以下四种取值:

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 
ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

接下来我们看看线程池的处理流程图:

Java并发编程之线程池的理解与使用

再接下来,我们看一张图来了解 ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor几个之间的关系:

Java并发编程之线程池的理解与使用

有兴趣的可以查查这些类中具体的方法,我这里就不一一列出了。

在java doc中,并不提倡我们直接使用ThreadPoolExecutor,而是使用Executors类中提供的几个静态方法来创建线程池:

Executors.newCachedThreadPool();        //创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE Executors.newSingleThreadExecutor();   //创建容量为1的缓冲池 Executors.newFixedThreadPool(int);    //创建固定容量大小的缓冲池三个静态方法的具体实现:

public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
从源码可以看出:这三个方法其实都是调用了 ThreadPoolExecutor,只不过参数都已配置好了。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,也使用的LinkedBlockingQueue;

newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

在实际开发中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法。

这是线程池源码中的原话:

  1. 如果当前正在运行的线程数小于核心线程数,即使线程 
  2.  池中有空闲的线程,仍创建一个新线程处理任务执行请求,即创建一个新的任务线程执行任务。

那么为什么不用空闲线程而要创建线程呢。答:因为线程池复用核心线程的方法是从任务队列中取任务,而在源码中,只有任务数超过核心线程数才能将任务放入任务队列。

参考资料:

http://blog.csdn.net/zhouhl_cn/article/details/7392607

http://blog.csdn.net/yinkai1205/article/details/47667971

http://www.cnblogs.com/dolphin0520/p/3932921.html