Java线程池ThreadPoolExecutor

Java线程池ThreadPoolExecutor

背景

本文基于JDK1.7的源码进行分析并解读。
ThreadPoolExecutor是Doug Lea大神开发的JUC提供的一类线程池工具,也是Java语言中应用场景最多的并发框架,可以说,几乎所有需要异步或者并发执行的,都可以使用Java线程池。

优点

合理的使用线程池能够带来的好处:
1.降低资源消耗:通过复用已经创建好的线程来降低线程创建和销毁的资源消耗
2.提高响应速度:任务到达时不需要等待线程创建就可以立即执行。
3.提高线程的可管理性:线程池可以统一管理、分配、调优和监控。
4.弹性伸缩,服务器通常有高峰期也有低峰期,线程池可以弹性伸缩,线程创建成功后长时间不使用可以自动回收,以减少系统资源的浪费;线程池的容量可以随时增长;
5.拒绝策略,线程数量有限而需要处理的任务很多,超出系统承载范围的任务的处理策略是什么,是拒绝还是阻塞等待

运行状态

在ThreadPoolExecutor线程池的设计中,线程池运行中一共有五个状态:
RUNNING:允许接收新任务并且处理队列中的任务
SHUTDOWN:不再接收新的任务,仅消化完队列中的任务
STOP:不仅不再接收新的任务,连队列中的任务都不再消化处理了,并且尝试中断正在执行任务的线程
TIDYING:所有任务被终止了,工作线程数workCount也被设为0,线程的状态也被设为TIDYING,并开始调用钩子函数terminated()
TERMINATED:钩子函数terminated()执行完毕
状态转换如下图所示:
在这里插入图片描述aaa
由图可知:线程池的状态转换是不可逆的

状态字

ThreadPoolExecutor把线程池状态和线程池容量打包成一个int型变量,如下图所示
状态字线程池状态位

状态高位值枚举正负性
RUNNING111负数(-536870912)
SHUTDOWN0000
STOP001正数(536870912)
TIDYING010正数(1073741824)
TERMINATED011正数(1610612736)

因此在状态值的排布上可以知道 TERMINATED > TIDYING > STOP >SHUTDOWN > RUNNING
ThreadPoolExecutor中的代码如下所示:

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
 
// 状态字的高比特位存放线程池状态信息
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;
 
// 打包/提取状态字信息
private static int runStateOf(int c)     { return c & ~CAPACITY; }
private static int workerCountOf(int c)  { return c & CAPACITY; }
  
// 判断当前线程池是否正在执行
private static boolean isRunning(int c) {
    return c < SHUTDOWN;
}

线程池主要执行流程

线程池执行流程
1.调用ThreadPoolExecutor的execute提交线程,首先检查有效线程数,如果有效线程数小于CorePoolSize,新创建线程执行任务。即使此时线程池中存在空闲线程
2.如果当前有效线程数大于等于CorePoolSize,那么将线程加入到BlockingQueue。
3.如果不能加入BlockingQueue,在小于MaxPoolSize的情况下创建线程执行任务。
4.如果有效线程数大于等于MaxPoolSize,新提交任务由RejectedExecutionHandler处理。
5.当线程池中有效线程数超过corePoolSize线程时,若线程空闲时间达到keepAliveTime,则会关闭空闲线程,直到线程数等于corePoolSize,之后空闲线程就不会关闭了.

1.线程池的创建

方式1.通过ThreadPoolExecutor的构造方法实现:

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @param threadFactory the factory to use when the executor
     *        creates a new thread
     * @param handler the handler to use when execution is blocked
     *        because the thread bounds and queue capacities are reached
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue}
     *         or {@code threadFactory} or {@code handler} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

具体解释一下上述参数:
corePoolSize:核心线程池大小
maximumPoolSize:线程池最大容量大小
keepAliveTime:线程池空闲时,线程存活的时间
TimeUnit:线程存活的时间单位
ThreadFactory:线程工厂
BlockingQueue:任务阻塞队列
RejectedExecutionHandler:线程拒绝策略

方式2.使用 Executors 创建线程池

1).newFixedThreadPool:创建固定大小的线程池

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

2).newSingleThreadPool:创建线程数为1的线程池

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

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

3).newCachedThreadPool:缓冲线程池

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

4).newScheduledThreadPool:定时线程池

 public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
   public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    } 

不推荐使用Executors 创建线程池

阿里巴巴java开发手册明确规定:
【强制】线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样
的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。

2.线程的提交

ThreadPoolExecutor的构造方法如上所示,但是只是做一些参数的初始化,ThreadPoolExecutor被初始化好之后便可以提交线程任务,线程的提交方法主要是execute和submit。
ThreadPoolExecutor的入口方法就是execute(Runnable commad)。该方法的执行逻辑如下:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
int c = ctl.get();
// 1. 如果当前线程池中线程总数少于核心线程数,则添加新线程到线程池中,
// 并且由新线程执行刚提交进来的任务
if (workerCountOf(c) < corePoolSize) {
    if (addWorker(command, true))
        return;
    c = ctl.get();
}
  
if (isRunning(c) && workQueue.offer(command)) {
    int recheck = ctl.get();
    // 2. 可能刚才在创建新线程成功的同时,线程池被关闭了,因此需要double-check,
    // 如果此时线程池已经被关闭了,那么回滚刚才被添加进来的任务
    if (! isRunning(recheck) && remove(command))
        reject(command);
    else if (workerCountOf(recheck) == 0)
        addWorker(null, false);
}
// 3. 如果此时核心线程数(corePoolSize)已经满了,并且任务队列也满了,
// 尝试增加线程到maximumPoolSize大小,如果仍然失败,执行拒绝策略
else if (!addWorker(command, false))
    reject(command);
    }

在上面的代码里面,ctl.get()方法、workerCountOf()、以及isRunning() 方法都是对之前提到的状态字进行读写的操作,这部分我们就不再展开给读者看了,有兴趣的读者可以自己了解一下。
接下来,我们看看addWorker都做了什么工作:

private boolean addWorker(Runnable firstTask, boolean core) {
    // 这部分省略的代码都是对状态字进行修改,添加并创建线程之前,
    // 需要递增work记数(此时需要线程安全地操作)
    ...
 
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        ...
        w = new Worker(firstTask); // 此处封装出了一个新的Work,这个类我们稍后会介绍
        final Thread t = w.thread;
        if (t != null) {
            ...
             
                // 获得线程池状态,如果线程池已经被关闭了,则不再创建新的线程
                int c = ctl.get();
                int rs = runStateOf(c);
 
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    if (t.isAlive()) // precheck that t is startable
                        throw new IllegalThreadStateException();
                    workers.add(w);
                    int s = workers.size();
                    ...
                    workerAdded = true;
            ...
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            // 如果任务启动或者提交到线程池失败,
            // 则执行回滚操作(从工作线程池中移除失败添加的worker、减少状态字中的任务计数)
            addWorkerFailed(w);
    }
    return workerStarted;
}

3.任务执行

任务执行在Worker类中,而Worker类是一个继承了Runnable接口的类。

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    ...
    public void run() {
        runWorker(this);
    }
    ...
}

可以看到Worker类中调用了外部的runWorker()方法。因此可以了解到,任务执行的主要逻辑,就是在外部的runWorker()方法中执行的

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    ...
    boolean completedAbruptly = true;
    try {
        while (task != null || (task = getTask()) != null) { // 循环读取任务
            ...
            try {
                beforeExecute(wt, task); // 用户实现的回调方法,任务启动前
                Throwable thrown = null;
                try {
                    task.run();// 任务执行
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } catch (Error x) {
                    thrown = x; throw x;
                } catch (Throwable x) {
                    thrown = x; throw new Error(x);
                } finally {
                    afterExecute(task, thrown); // 用户实现的回调方法,任务执行后
                }
            } finally {
                task = null;
                w.completedTasks++;
                ...
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

beforeExecute和afterExecute是两个钩子方法,在里面指定了当线程开始执行和完成执行以后执行的动作,需要开发者实现。
另外需要注意的还有runWorker方法内调用的getTask()方法,在该方法内部,如果发生以下情况将会返回null,终止工作线程的执行循环:
1)当前线程数即将超过maxPoolSize
2)线程池被关闭
3)当前线程数大于corePoolSize且小于maxPoolSize,并关切从BlockingQueue取数据超过了超时时间(默认60秒)
代码实现如下:

private Runnable getTask() {
    boolean timedOut = false; // Did the last poll() time out?
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);
 
        // 校验当前线程池状态
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }
 
        boolean timed;      // Are workers subject to culling?
 
        for (;;) {
            int wc = workerCountOf(c);
            timed = allowCoreThreadTimeOut || wc > corePoolSize;
 
            if (wc <= maximumPoolSize && ! (timedOut && timed))
                break;
            if (compareAndDecrementWorkerCount(c))
                return null;
            c = ctl.get();  // Re-read ctl
            if (runStateOf(c) != rs)
                continue retry;
            // else CAS failed due to workerCount change; retry inner loop
        }
 
        try {
            // 如果线程超过指定时间内(默认60秒)没有获取到任务,说明有线程即将过期
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

4.任务拒绝

如果线程被提交到线程池时,当前线程池出现以下情况的任一一种情况:
1)线程池任务队列已经满了
2)线程池被关闭了(调用了shutdown函数或者shutdownNow函数)
都将会调用提前设置好的拒绝策略,ThreadPoolExecutor中总共提供了四种策略:
1)AbortPolicy(中止):该策略将会直接抛出RejectedExecutionException异常,调用者将会获得异常;
2)DiscardPolicy(抛弃):使用该策略,线程池将会悄悄地丢弃这个任务而不被调用者知道;
3)CallerRunsPolicy(调用者运行):该策略既不会抛弃任务也不会抛出异常,而是将这个任务退回给调用者,从而降低新任务的流量;
4)DiscardOldestPolicy(抛弃最旧的):该策略将会抛弃下一个即将轮到执行的任务,那么“抛弃最旧”的将导致抛弃优先级最高的任务,因此最好不要把“抛弃最旧的”饱和策略和优先级队列放在一起使用;

public static class CallerRunsPolicy implements RejectedExecutionHandler {

        public CallerRunsPolicy() { }

        /**
         * 直接在调用者线程中运行当前被丢弃的任务,要注意这里是调用Runnable的run()方法,而不是start()方法启动线程,run()以普通方法的形式在主线程中执行任务,会阻塞
         * 后面es.submit(new MyTask(i))方法的执行
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }


    public static class AbortPolicy implements RejectedExecutionHandler {

        public AbortPolicy() { }

        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }


    public static class DiscardPolicy implements RejectedExecutionHandler {

        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }


    public static class DiscardOldestPolicy implements RejectedExecutionHandler {

        public DiscardOldestPolicy() { }

        /**
         * 将丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                e.getQueue().poll();
                e.execute(r);
            }
        }
    }

5.线程池销毁

ThreadPoolExecutor提供了两种方法销毁线程池,分别是shutdown()和shutdownNow()
shutdown()方法仅仅是把线程池的状态置为SHUTDOWN,并且拒绝之后尝试提交进来的所有请求,但是已经在任务队列里的任务会仍然会正常消费。
而shutdownNow()方法的表现显得更加简单粗暴,它会强行关闭ExecutorService,也会尝试取消正在执行的任务,并且返回所有已经提交但尚未开始的任务,开发者可以将这些任务写入日志保存起来以便之后进行处理,另外尝试取消正在执行的任务仅仅是尝试对执行线程进行中断,具体的线程响应中断策略需要用户自己编写。代码实现如下:

public List<Runnable> shutdownNow() {
    List<Runnable> tasks;
    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        checkShutdownAccess();
        advanceRunState(STOP);
        interruptWorkers();
        tasks = drainQueue();
    } finally {
        mainLock.unlock();
    }
    tryTerminate();
    return tasks;
}

小心踩坑:线程池经验谈

不要使用ThreadLocal

不要在ThreadPoolExecutor线程池中使用ThreadLocal,因为在ThreadPoolExecutor中,线程是复用的,因此在这里使用ThreadLocal会被多个task共享,因此可能会带来脏数据污染。需要小心使用

合理设置corePoolSize的值

一般来说确定线程池的大小需要考虑CPU数量,内存大小,JDBC连接等因素。在《java并发编程实践》一书中给出了一个估算线程池大小的经验公式:
Ncpu=CPU的数量
Ucpu=目标CPU的使用率,0<=Ucpu<=1
W/C=等待时间与计算时间的比率
为保持处理器达到期望的使用率,最优的线程池的大小等于:
Nthreads=NcpuUcpu(1+W/C)
在java中,可以通过
Runtime.getRuntime().availableProcessors()
取得可以CPU数量。

线程中断

虽然ThreadPoolExecutor提供了shutdownNow()方法,在调用该方法后会尝试中断所有线程,但是该中断并不能保证线程一定会就此终止,因此,需要开发者实现线程中断的策略。关于这部分的内容,在Doug Lea的《Java Concurrency In Practice》的7.1.2节已经进行了完整的讨论,笔者在这里就不再赘述了。

finalize函数

尤其需要注意的是,ThreadPoolExecutor有一个finalize函数,具体实现如下:

protected void finalize() {
    shutdown();
}

在该方法中调用了shutdown()函数,因此,如果你并不是真正希望停止线程池的执行,那么就不要让线程池离开你代码的作用范围。

参考:
https://www.jianshu.com/p/79cfb6475a91
https://www.cnblogs.com/intsmaze/p/9432199.html
https://www.jianshu.com/p/ade771d2c9c0

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值