(十)ThreadPoolExecutor 源码分析 —— 线程池

版权声明:本文为博主原创文章,未经博主允许不得转载。
本文纯个人学习笔记,由于水平有限,难免有所出错,有发现的可以交流一下。

一、生产者消费者模式

我们在多线程开发中经常会使用到生产者消费者模式,所以在这边先进行生产者消费者模式的简单介绍。

为什么要使用生产者消费者模式:

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。
引用自 https://my.oschina.net/xianggao/blog/390411

这边采用同步进行实现一个简单的生产者消费者模式。

Product :

public class Product {

    private ArrayList<String> list;
    private int maxSize;

    public Product(int maxSize) {
        this.list = new ArrayList<String>();
        this.maxSize = maxSize;
    }

    /**
     * 生产
     */
    public void produce() {
        try {
            synchronized (list) {
                while (list.size() == maxSize) {
                    list.wait();
                }
                list.add(System.currentTimeMillis() + "");
                System.out.println(Thread.currentThread().getName() + "生产一个");
                list.notifyAll();
            }
        } catch (InterruptedException e) {
        }
    }

    /**
     * 消费
     */
    public String consume() {

        try {
            synchronized (list) {
                while (list.size() == 0) {
                    list.wait();
                }
                String consume = list.remove(0);
                System.out.println(Thread.currentThread().getName() + "消费一个");
                list.notifyAll();
                return consume;
            }
        } catch (InterruptedException e) {
            return null;
        }
    }

}

Test :

public class Test {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        final Product product = new Product(10);

        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(true){
                    product.produce();
                }
            }
        }).start();     

        new Thread(new Runnable() {

            @Override
            public void run() {
                // TODO Auto-generated method stub
                while(true){
                    product.consume();
                }
            }
        }).start();
    }
}

二、ThreadPoolExecutor

1.构造函数

ThreadPoolExecutor 构造函数有多个重载,这边主要是讲解一下各个参数的意义。

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

参数说明:

corePoolSize:核心线程数
maximumPoolSize:最大线程数
keepAliveTime:线程超时时间,超时时候,线程会被释放回收
unit:线程超时时间的单位,有TimeUnit.MILLISECONDS(ms)、TimeUnit. SECONDS(s)等
workQueue:任务队列
threadFactory:线程工厂接口,只有 new Thread(Runnable r)方法,创建新线程
handler:当任务执行失败时,使用 handler 进行通知

2.处理逻辑

一个线程池里面的线程分为核心线程和非核心线程。
1.如果当前线程池中数量小于 corePoolSize,创建核心线程执行任务。
2.如果当前线程池中数量大于 corePoolSize,线程池中线程小于 maximumPoolSize,创建非核心线程执行任务。
3.如果当前线程池中数量大于 corePoolSize,线程池中线程等于 maximumPoolSize,任务队列没满,加入任务队列进行等待。
4.如果当前线程池中数量大于 corePoolSize,线程池中线程等于 maximumPoolSize,任务队列满了,交给错误 handler 进行处理。
5.非核心线程空闲时间超过 keepAliveTime,就会进行回收释放。

3.线程池状态与数量

在 ThreadPoolExecutor 中有一个 AtomicInteger 的变量 ctl,ctl 里面存储有两个数据信息,当前线程池的状态 runState 和有效的线程数量 workCount 。AtomicInteger 是一个32位的整数,其中高3位表示线程池状态,低29位表示线程数量。

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

线程池状态有5个。

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;
//所有状态执行完毕,调用 terminated 方法(该方法需重载)
private static final int TIDYING    =  2 << COUNT_BITS;
//terminated 方法执行完毕
private static final int TERMINATED =  3 << COUNT_BITS;

ThreadPoolExecutor 提供了三个方法,可以对 ctl 数据进行封装解析

// 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; }

3.源码分析

线程池 ThreadPoolExecutor 的执行,也是通过 execute 方法进行的,我们先查看这个方法。

ThreadPoolExecutor 的 execute:

   public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();

        int c = ctl.get();
        //判断当前线程数小于 corePoolSize,
        if (workerCountOf(c) < corePoolSize) {
            //添加核心线程(true 表示核心线程)
            if (addWorker(command, true))
                //添加成功,返回
                return;
            c = ctl.get();
        }
        //判断当前线程池状态为 RUNNING,只有 RUNNING 才可以接收新任务
        //然后把 command 添加到任务队列
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            //再次判断当前线程池状态为 RUNNING,不是的话,移除 command 任务
            if (! isRunning(recheck) && remove(command))
                reject(command);

            //如果在运行阶段,而且 workCount 为 0,调用 addWorker 方法添加新线程
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        //不能接受新任务,就添加新线程
        else if (!addWorker(command, false))
            //添加新线程失败,就 reject
            reject(command);
    }

在 execute 中有一大段注释,这段注释就是对三个判断条件的讲解。这边进行简易的翻译。
注释翻译:
(1)如果正在运行的线程数小于 corePoolSize,试着开启一个新的线程,并且把 command 作为第一个任务,然后调用 addWorker。检查 runState 和 workerCount,为了防止在线程不应该被添加的时候进行了添加操作,将会返回 false。
(2)如果一个任务可以被添加到队列中,我们仍然需要再次检查是否我们真的需要要添加一个线程(有可能在上次检查完之后,有线程被回收)或者我们进入该方法后,线程池关闭了。所以我们需要再次检查状态,如果有必要的话,进行回滚队列(就是指 remove 操作)或者启动一个新的线程。
(3)如果我们不能对任务进行排队,那么我们尝试添加一个新的线程。如果失败了,那么我们可以知道,线程池关闭或饱和了,因此拒绝这项任务。

这里主要调用了两个方法,addWorker 和 reject,reject相对比较简单,我们先来查看。

ThreadPoolExecutor 的 reject:

    final void reject(Runnable command) {
        handler.rejectedExecution(command, this);
    }

handler 是我们传递进来的,是接口 RejectedExecutionHandler 的一个实现。出现错误,通过 handler ,返回给用户进行处理。默认实现是 AbortPolicy,AbortPolicy 直接抛出一个异常。

AbortPolicy :

   public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

还有一个重要的方法是 addWorker,这个方法是新建一个线程,其中第二个参数表示创建的线程是否为核心线程。

ThreadPoolExecutor 的 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.
            //rs >= SHUTDOWN,即线程池状态不为 RUNNING
            //! (rs == SHUTDOWN && firstTask == null &&! workQueue.isEmpty())
            //当线程池状态为 SHUTDOWN 的时候,前面说过,这时候不再接收新任务,但是可以处理队列中的任务。
            //所以要求 firstTask 为空,并且队列 workQueue 不为空
            //firstTask 不为空,表示新增任务,与 SHUTDOWN 状态不符,所以 firstTask 必须为空
            //workQueue 为空,表示任务都已经处理完,所以不允许 workQueue 为空
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            //进入内循环
            for (;;) {
                int wc = workerCountOf(c);
                //CAPACITYA 为 2^29-1,一般不会超过
                //当前线程数与核心线程数或者最大线程数进行比较(根据 core)
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                //只是对计数器进行 +1 操作
                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
            }
        }
        //只有在这两种情况下,程序才会跳出上方的内循环:
        //核心线程,当前线程数量小于 CAPACITY 和 corePoolSize 中的较小值 
        //非核心线程,当前线程数量小于 CAPACITY 和 maximumPoolSize 中的较小值。 

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //创建新线程,主要是通过 Worker 类,
            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());

                    //rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)
                    //判断线程池是否允许处理任务
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //往工作集合 workers 中添加新建的 Worker
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                if (workerAdded) {
                    //添加成功,就直接启动
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //启动线程失败,则进行回滚
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

在这里,启动线程主要是依赖于 Worker 这个类,我们先查看这个类的实现。

Worker:

    //Worker 继承于 AbstractQueuedSynchronizer 是为了实现阻塞锁和其他同步工具
    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)
         */
         //在这里获取到 ThreadPoolExecutor 的线程工厂来创建线程
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker. */
        //实现的 Runnable 接口,真正的执行任务。
        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) {
                }
            }
        }
    }

在 Worker 的 run 方法中,我们可以看到,真正执行任务的方法是 runWorker 这个方法。

ThreadPoolExecutor 的 runWorker:

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();

        //w.firstTask 就是我们创建 Worker 时候传递的 Runnable 
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //拿到的任务不为 null
            //第一次进来肯定不为空
            //完成任务后 task 为空,就会执行 getTask,从任务队列中去拿取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                // If pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                //如果线程池已经停止了,那么中断当前线程,停止任务
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                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 置空,再次循环时,就会去队列中获取任务
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

当第一次进入 while 循环的时候,task 不为空。runWorker 先执行一个 task 任务,执行完毕之后,会把 task 置空,然后再次 进入 while 循环,这时候 task 为空,会调用 getTask 从任务队列中获取任务,如果获取到了,则继续执行,执行完毕置空,再次循环。当 getTask 返回 null 的时候,那么就会跳出 while 循环,结束 runWorker 这个方法,从而结束 run 方法,即当前线程结束。

我们前面提到,非核心线程在空闲一段时间后才会进行释放,核心线程一直不会释放,这是因为在 getTask 中进行了堵塞。接下来查看 getTask 的实现。

getTask:

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            //必要时,检查任务队列是否为空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            //当前线程是否可以被释放
            //allowCoreThreadTimeOut 是表示核心线程是否有超时时间,可以通过方法进行设置
            // wc > corePoolSize 即当前线程池中的线程数大于核心线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //当前线程是否可以被释放
                //可以的话,调用 poll 方法,等待 keepAliveTime 
                //poll 传下去的时间单位写死,是在 ThreadPoolExecutor 构造函数中的进行了转换 
                //不可以被释放的话,调用 take 进行堵塞等待
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

三、线程池

1.线程池的优点

线程池相比线程有以下优点:

1、避免每次new Thread 新建对象
2、线程统一管理,重用存在的线程,减少对象创建、消亡的开销。
3、可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。

2.常见的线程池

线程池一般都是通过 Executors 的工厂方法进行创建,我们常用的线程有4种:

CachedThreadPool:
Executors 的创建方法:

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

CachedThreadPool 没有核心线程,最大线程数为 Integer.MAX_VALUE,基本上线程数达不到这个数,所以只要有需要,就会马上新建线程。SynchronousQueue 不持有任务,只要一有任务,马上交给线程去执行,设置的超时时间为 60s,如果有线程在等待的话,就使用这个线程,如果没有线程在等待,就会去新创建线程。每个线程空闲 60s 后就会被释放。

FixedSizeThreadPoolc:

Executors 的创建方法:

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

创建一个定长线程池,所有线程都是核心线程,这样所有线程创建后都不会销毁。可控制线程最大并发数,由于使用 LinkedBlockingQueue,任务队列没有限制,所有超出的任务会在队列中等待。

ScheduledThreadPool:
Executors 的创建方法:

  public static ScheduledExecutorService newScheduledThreadPool(
            int corePoolSize, ThreadFactory threadFactory) {
        return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
    }

核心线程数量固定,为corePoolSize ,最大线程数为 Integer.MAX_VALUE,该线程池主要用于执行周期性的任务或在延时一段时间后执行任务。

SingleThreadPool:
Executors 的创建方法:

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

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。由于使用 LinkedBlockingQueue,任务队列没有限制,所有超出的任务会在队列中等待。

猜你喜欢

转载自blog.csdn.net/qq_18983205/article/details/81511322