深入理解Java线程池

在编程中,我们通常在需要用到线程时会直接创建一个线程,这样实现起来非常方便,但是同时也会带来这样一个问题:
如果需要大量并发的线程,每个线程创建后,执行时间很短的任务就结束线程,那么这将会大大降低系统的运行效率,因为频繁地创建和销毁线程会浪费大量的时间和资源。
在Java中,我们可以通过使用线程池来避免这种情况。下面我们将从线程池核心类ThreadPoolExecutor类对Java线程池进行分析。

1. 线程池ThreadPoolExecutor类

1.1 ThreadPoolExecutor类构造方法

java中的 java.util.concurrent.ThreadPoolExecutor 类是线程池的核心类,下面来看一下ThreadPoolExecutor类的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

如上述代码所示,ThreadPoolExecutor类提供了4种构造方法,而实际上,通过查看ThreadPoolExecutor类的构造方法源代码,可以得知前3种构造器都是调用第4种构造器来完成ThreadPoolExecutor的初始化工作。
下面我们来详细介绍构造器中各个参数的含义:

int corePoolSize:核心池的大小。在创建线程池后,线程池中是没有线程的。除非调用了prestartAllCoreThreads()方法或者prestartCoreThread()方法,从这两个方法的名字也可以推断出,是预创建线程的意思,即在任务还没有到来之前在线程池中创建corePoolSize个线程或者一个线程。在默认情况下,新创建线程池中线程个数为0.
当线程池中线程个数小于corePoolSize时,每提交一个新任务,线程池将会新创建一个线程,即使线程池中其他工作线程处于空闲状态。
当线程池中线程的个数大于corePoolSize且小于maxmumPoolSize时,新提交的任务将会被放置到任务队列中,只有当任务队列满之后,才会新创建一个线程。
通过getCorePoollSize()方法可以获取线程池的设置的核心线程上限值。通常在线程池创建时,corePoolSize就被设置好了,但是在线程池运行的过程中也可以通过调用setCorePoolSize方法动态地调整线程池的核心线程数。

int maximumPoolSize:线程池的最大线程数。这个参数代表该线程池最多能够创建多少个线程。
通过getMaximumPoolSize()方法可以获取线程池所设置的最大线程个数。一般情况下,maximumPoolSize在线程池创建时就已经设置好了,但是在线程池运行的过程中也可以通过调用setMaximumPoolSize()方法动态地调整线程池的最大线程数。

long keepAliveTime:当线程没有任务执行时最多保持多长时间会被终止。如果当前线程池中线程个数大于corePoolSize,那么超过corePoolSize个数的线程在终止之前能够保持空闲的最长时间。这就提供了一种手段,用于在线程池不活跃时减少资源的浪费。该参数也可以通过调用setKeepAliveTime方法进行动态地调整。
在默认情况下,只有当线程池中线程个数大于corePoolSize时,该参数才会产生作用,应用于过量的线程。但是,也可以通过调用allowCoreThreadTimeOut(boolean)方法,使得该参数同样能够应用于核心线程。

TimeUnit unit:参数keepAliveTime的时间单位,有7种取值,TimeUnit类的7种静态属性如下:

TimeUnit.DAYS;               //天
TimeUnit.HOURS;             //小时
TimeUnit.MINUTES;           //分钟
TimeUnit.SECONDS;           //秒
TimeUnit.MILLISECONDS;      //毫秒
TimeUnit.MICROSECONDS;      //微妙
TimeUnit.NANOSECONDS;       //纳秒

BlockingQueue<Runnable> workQueue:阻塞队列,用于存储将要等待执行的任务。任何BlockingQueue都能够用于存储提交的任务。
常用的三种类型队列如下:

SynchronousQueue:该队列不会保存提交的任务,而是直接创建一个线程来执行该任务,这通常要求线程池的maximumPoolSize是无限的。换句话说,就是允许无限地创建线程,即使这些线程无法获得CPU。这对于处理那些存在相互依赖关系的任务来说是合适,可以避免产生死锁,因为可以快速执行完毕释放某些资源。

LinkedBlockingQueue: 基于链表的先进先出队列。如果在创建队列时没有指定队列的大小,那么队列的大小默认为Integer.MAX_VALUE。这就意味着该队列为无限队列,当核心线程数都在工作时,将会造成新提交的任务在队列中长时间等待。没有新的线程将会被创建,因为队列太长无法装满,从另一个方面来说,maximumPoolSize参数无法产生作用。该队列队列相互独立为任务来说是合适的,例如说WEB服务请求。

ArrayBlockingQueue:基于数组的先进先出队列。创建该队列必须指定队列的大小,也就是说对队列是有界的。需要在maximunPoolSize和queueSize之间进行权衡。

ThreadFactory threadFactory:线程工厂,用来创建新线程。如果没有指定线程工厂,将会使用默认线程工厂Excutors.defaultThreadFactory。

RejectedExecutionHandler handler:拒绝任务的处理策略。当线程池已经关闭,或者线程池中线程数已经达到maximumPoolSize且任务队列已经满,新提交的任务将会被拒绝执行。线程池提供的四种拒绝任务的处理策略如下:

ThreadPoolExecutor.AbortPolicy:默认的拒绝处理策略,在拒绝任务的同时将会抛出RejectedExecutionException运行时异常;

ThreadPoolExecutor.DiscardPolicy:简单地丢弃任务;

ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列中最前面的任务,并尝试重新执行该任务;

ThreadPoolExecutor.CallerRunsPolicy:在调用execute方法的线程中执行该任务,这就提供了一种简单的反馈机制用于降低提交新任务的速率。

1.2 ThreadPoolExecutor类层次关系

ThreadPoolExecutor类的定义如下:

public class ThreadPoolExecutor extends AbstractExecutorService{....

由上述代码可见,ThreadPoolExecutor继承了AbstractExecutorService类。AbstractExecutorService类的定义如下:

public abstract class AbstractExecutorService implements ExecutorService {...

由上述代码可见,AbstractExecutorService类是一个抽象类,并且实现了ExecutorService接口。ExecutorService接口的定义如下:

public interface ExecutorService extends Executor{....

由上述代码可见,ExecutorService接口继承了Executor接口。Excutor接口的定义如下:

public interface Executor{...

Executor是一个顶层接口。

2. 线程池的设置

2.1 线程池的状态

ThreadPoolExecutor类对线程池状态的定义如下:

private static final int COUNT_BITS = Integer.SIZE - 3;

private static final int RUNNING    = -1 << COUNT_BITS;
private static final int SHUTDOWN   =  0 << COUNT_BITS;
private static final int STOP       =  1 << COUNT_BITS;
private static final int TIDYING    =  2 << COUNT_BITS;
private static final int TERMINATED =  3 << COUNT_BITS;

由上述代码可见,线程池一共有RUNNING, SHUTDOWN, STOP, TIDYING, TERMINATED这五种状态:

RUNNING:在该状态下,线程池接受新任务,并执行队列中等待执行的任务;

SHUTDOWN:在该状态下,线程池不再接受新任务,但是会继续处理已经在队列中的任务;

STOP:在该状态下,线程池不接受新任务,也不会去处理已经在队列中的任务,并且中断正在执行的任务;

TIDYING:在该状态下,线程池中所有任务都已经终止,工作线程数为0,并将自动执行内部的terminated()方法;

TERMINATED:内部的terminated()方法执行完毕。

2.2 线程池状态间转变

RUNNING -> SHUTDOWN:调用shutdown方法;

(RUNNING or SHUTDOWN) -> STOP:调用shutdownNow方法;

SHUTDOWN -> TIDYING:当队列和线程池均为空时;

STOP -> TIDYING:当线程池为空;

TIDYING -> TERMINATED:当内部的terminated方法执行完毕;

2.3 线程池的关闭

ThreadPoolExecutor类提供了两个方法用于关闭线程池,分别是shutdown()和shutdwonNow()方法:
shutdown():执行该方法后,不会立即终止线程,会等待任务队列中所有任务完成,但是不在接受新任务;
shutdownNow():执行该方法后,将会阻止新提交的任务,同时中断正在运行的线程。另外还移除任务队列中的任务,并添加到List列表中返回。

3. execute方法内部实现

在ThreadPoolExecutor类中,最核心的任务提交方法是execute方法,下面我们来解析execute方法的内部实现:

public void execute(Runnable command) {
        if (command == null)    //如果传递的任务为null,execute方法将会抛出NullPointException异常
            throw new NullPointerException();
        int c = ctl.get();    //ctl为AtomicInteger类型变量,用来保持当前线程池的状态
        if (workerCountOf(c) < corePoolSize) {   //workerCountOf返回当前状态下工作线程个数
            if (addWorker(command, true))   //调用addWorker函数,如果成功添加了线程,返回true; 否则返回false
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {   //如果该线程池处于RUNNING状态,但是已经达到corePoolSize个线程,则添加到任务队列中
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))  //从队列中remove新提交的任务
                reject(command);     //抛弃任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);   
        }
        else if (!addWorker(command, false))  //如果线程池不是处于RUNNING状态,或者任务队列已满,则调用addWorker,参数设置为false,尝试创建余量的线程
            reject(command);
}

addWorker函数的源代码如下:

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();   //获取当前线程池状态
            int rs = runStateOf(c);  //将状态c的低29位置0

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;   //如果处于STOP状态,或者为SHUTDOWN状态且传递的任务参数不为null,直接返回false

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))   //如果core为true,代表要创建核心线程;为false,代表创建余量的线程
                    return false;    //无法创建核心线程或者余量线程
                if (compareAndIncrementWorkerCount(c))   //CAS操作增加当前线程池的工作线程数
                    break retry;
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)   //状态改变
                    continue retry;
                // else CAS failed due to workerCount change; retry inner loop
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            w = new Worker(firstTask);   //新建Worker对象,Worker类是内部类
            final Thread t = w.thread;   //w.thread是通过线程工厂ThreadFactory来构造的
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();   //获取ThreadPoolExecutor类中的mainLock锁
                try {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        workers.add(w);  //添加新Worker对象到HashSet类型变量workers中
                        int s = workers.size();
                        if (s > largestPoolSize)  //largestPoolSize用于记录线程池所达到过的最大线程数
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

4. 线程池大小的配置策略

一般需要根据任务的类型来配置线程池的大小:
如果是CPU密集型,通常需要尽可能的压榨CPU,线程的个数可以设置为 Ncpu+1;
如果是IO密集型,参考值可以设为 2*Ncpu;

参考博客:
http://www.cnblogs.com/dolphin0520/p/3932921.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值