Java并发编程:线程池的使用

Java并发编程:线程池的使用

目录大纲:
* Java中的ThreadPoolExecutor类
* 线程池实现原理
* 线程池状态
* 任务的执行
* 线程池中的线程初始化
* 任务缓存队列及排队策略
* 任务拒绝策略
* 线程池的关闭
* 线程池容量的动态调整
* 自定义线程池
* 合理配置线程池的大小
* FIFO优先级线程池


Java中的ThreadPoolExecutor类

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下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: 指的是保留的线程池大小;
maximumPoolSize: 指的是线程池的最大大小;
keepAliveTime: 指的是空闲线程结束的超时时间;
unit: 是一个枚举,表示 keepAliveTime 的单位;
workQueue: 表示存放任务的队列;
threadFactory: 线程工厂,主要用来创建线程;
handler: 表示当拒绝处理任务时的策略;


我们可以从线程池的工作过程中了解这些参数的意义。线程池的工作过程如下:

  1. 线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。
  2. 当调用 execute() 方法添加一个任务时,线程池会做如下判断:
    a. 如果正在运行的线程数量小于 corePoolSize,那么马上创建线程运行这个任务;
    b. 如果正在运行的线程数量大于或等于 corePoolSize,那么将这个任务放入队列。
    c. 如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize,那么还是要创建线程运行这个任务;
    d. 如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize,那么线程池会抛出异常,告诉调用者“我不能再接受任务了”。
  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
  4. 当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
    这样的过程说明,并不是先加入任务就一定会先执行。假设队列大小为 10,corePoolSize 为 3,maximumPoolSize 为 6,那么当加入 20 个任务时,执行的顺序就是这样的:首先执行任务 1、2、3,然后任务 4-13 被放入队列。这时候队列满了,任务 14、15、16 会被马上执行,而任务 17-20 则会抛出异常。最终顺序是:1、2、3、14、15、16、4、5、6、7、8、9、10、11、12、13。

线程池实现原理

下面具体介绍线程池的方法以及属性:

线程池状态

要读懂线程池的状态,需要知道几个属性:

/*
 *可以将这个参数看成是一个三十二位的二进制数,
 *其中前三位表示线程池的状态,
 *后二十九位表示线程池中工作线程的数量
 */
    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;

    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;  //RUNNING状态表示线程池可以接受任务正常工作
    private static final int SHUTDOWN   =  0 << COUNT_BITS;  //SHUTDOWN状态表示线程池不接受任务,但如果阻塞队列中还有任务,会将阻塞队列中的任务执行完
    private static final int STOP       =  1 << COUNT_BITS;  //STOP状态表示线程池不接受任务,也不会执行阻塞队列中的任务,即使阻塞队列中还存在任务
    private static final int TIDYING    =  2 << COUNT_BITS;  //TIDYING状态表示所有任务都结束了,workerCount为0,调用terminated()方法
    private static final int TERMINATED =  3 << COUNT_BITS;  //TERMINATED状态terminated()方法调用完成

    // Packing and unpacking ctl
    private static int runStateOf(int c)     { return c & ~CAPACITY; }  //获取线程池的状态
    private static int workerCountOf(int c)  { return c & CAPACITY; }  //获取线程池工作线程的数量
    private static int ctlOf(int rs, int wc) { return rs | wc; }  //用于切换线程池状态,必要的时候改变工作线程的数量
任务的执行

execute方法是线程池执行任务的入口,execute方法的入参是必须要实现了Runnable接口的实现类。在了解任务提交以及任务执行之前,我们先peek一下ThreadPoolExecutor类中其他的一些比较重要成员变量:

private final BlockingQueue<Runnable> workQueue;   //任务缓存队列,用来存放等待执行的任务
private final ReentrantLock mainLock = new ReentrantLock();   //线程池的主要状态锁,对线程池状态(比如线程池大小、runState等)的改变都要使用这个锁
private final HashSet<Worker> workers = new HashSet<Worker>();  //用来存放工作集
private volatile long  keepAliveTime;    //线程存活时间
private volatile boolean allowCoreThreadTimeOut;   //是否允许为核心线程设置存活时间
private volatile int   corePoolSize;     //核心池的大小(即线程池中的线程数目大于这个参数时,提交的任务会被放进任务缓存队列)
private volatile int   maximumPoolSize;   //线程池最大能容忍的线程数
private volatile RejectedExecutionHandler handler; //任务拒绝策略
private volatile ThreadFactory threadFactory;   //线程工厂,用来创建线程
private int largestPoolSize;   //用来记录线程池中曾经出现过的最大线程数
private long completedTaskCount;   //用来记录已经执行完毕的任务个数

下面在 看看execute方法:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

在execute方法中没有看到要执行任务的地方,在这个方法里只是做了条件判断,如果满足
条件的话,就交给addWorker处理,并且根据判断条件的不同,传给addWorker方法的参数也不同。总结一下execute方法的执行逻辑:

1.当工作线程数量小于核心线程数量的时候,会将任务交给addWorker方法,addWorker方法会创建新的线程来处理这个任务
2.当工作线程数量大于和核心线程数量并且线程池的工作状态是running的时候,会将任务放入到阻塞队列中
3.当阻塞队列已经满了,会将任务交给addWorker方法处理
4.当交给addWorker方法处理失败或是线程池的状态不是running的时候,会调用线程池的拒绝策越。

execute方法其实是线程池工作的一个大体思路,具体细节我们可以看看addWorker方法。

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    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);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                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);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //当将任务放到任务队列成功后,启动工作线程t.start(),执行firstTask任务
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

所以,任务的启动是在addWorker方法,而具体的逻辑得看Worker。

为了弄明白t.start(); 我们继续往下看Worker的源码:

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;
        /** Per-thread task counter */
        volatile long completedTasks;

        /**
         * Creates with given first task and thread from ThreadFactory.
         * @param firstTask the first task (null if none)
         */
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        public void run() {
            runWorker(this);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

代码不多,主要看构造方法,保存了传进来的firstTask,以及通过线程工厂threadFactory实例化一个新的线程,也就是真正的工作线程。

回顾上面的步骤:
1. execute(); 就像一个栅栏,把要执行的任务,根据线程池的配置,划分好是否立刻执行/加入队列,亦或者执行拒绝策略,为后面要做的事情划分清楚;
2. addWorker(); 就是负责开启任务,任务的逻辑封装到Worker内部,我们可以将Worker看成就是一个工作线程,里面包含了执行任务和从阻塞队列中取任务的逻辑(就在runWorker(this)方法里面哦)。

自定义线程池

对于处理些特殊的业务,我们有时会严格要求,发起的每个请求(execute)都是顺序执行的,但是同时有需要具备一定的优先级,也就是FIFO&PRIORIT。
类结构一览:

  1. Priority 枚举类,标明任务的优先级,可实现优先执行
  2. PriorityRunnableBase 优先级基础类,排序的依据,实现Runnable, Comparable
  3. PriorityRunnable 具体业务runnable
  4. FifoPriorityThreadPoolExecutor工作线程池

FifoPriorityThreadPoolExecutor线程池的构造方法:

//代码片段来自FifoPriorityThreadPoolExecutor
   static {
        mThreadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.MILLISECONDS,
                new PriorityBlockingQueue<Runnable>(), sThreadFactory);
        mThreadPoolExecutor.allowCoreThreadTimeOut(true);
    }

FifoPriorityThreadPoolExecutor执行任务:

//代码片段来自FifoPriorityThreadPoolExecutor
public void execute(Runnable runnable) {
        mThreadPoolExecutor.execute(runnable);
    }

设计中最重要的是PriorityRunnableBase,下面看看代码:

/**
 * Created by JiangYiDong on 2018/1/19.
 */

public abstract class PriorityRunnableBase implements Runnable, Comparable<PriorityRunnableBase> {

    /**
     * 数字越大,优先级越高
     */
    protected Priority priority;
    private int order;
    private static final AtomicInteger ordering = new AtomicInteger();

    public PriorityRunnableBase(Priority priority) {
        this.priority = priority;
        this.order = ordering.getAndIncrement();
    }

    @Override
    public int compareTo(PriorityRunnableBase another) {
        return this.priority.ordinal() < another.priority.ordinal() ? 1
                : this.priority.ordinal() > another.priority.ordinal() ? -1 : this.order - another.order;
    }
}

注意:执行任务方法务必使用execute,具体请看线程池execute与submit的区别。

GitHub代码地址:
https://github.com/AriesJiang/AriesDemo/tree/master/libcountdownlatch/src/main/java/com/aries/java/concurrent/pool/priority/queue

GitHub笔记:
https://github.com/AriesJiang/AriesDemo/blob/master/note/2018-05-27-%E7%BA%BF%E7%A8%8B%E6%B1%A0%E7%9A%84%E4%BD%BF%E7%94%A8.md

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值