java线程池实现原理的源码分析

java 中线程池的核心实现是ThreadPoolExecutor ,那么这个对象是如何实现的呢?在分析前一般最大的疑问是:

  • 1、线程池的中线程对象如何被复用的?

    猜测:线程池的中线程对象的的run方法应该是一个死循环,这样才能保证运行完一个任务后,线程对象不会被关闭

  • 2、线程池如何优雅的替换的线程要执行的任务?

    猜测:应该是通过某种方法不停的获取不同的任务对象(Runnable ),然后线程对象调用该runnable 实例的run()方法

一、ThreadPoolExecutor 结构分析

UML
在这里插入图片描述

这里我们采用自顶向下的模式来分析下整个类的功能。首先最顶层的接口为Executor ,其基本的定义如下:

public interface Executor {

    /**
     * Executes the given command at some time in the future.  The command
     * may execute in a new thread, in a pooled thread, or in the calling
     * thread, at the discretion of the {@code Executor} implementation.
     *
     * @param command the runnable task
     * @throws RejectedExecutionException if this task cannot be
     * accepted for execution
     * @throws NullPointerException if command is null
     */
    void execute(Runnable command);
}

只定义了一个execute的方法,用来执行一个新的线程任务。

java.util.concurrent.ExecutorService 接口的方法定义:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eDzv3hqr-1591005776364)(assets/image-20200601101020309.png)]

ExecutorService 接口新增了关闭线程池的方法(shutdown)、执行带返回结果的线程任务方法(submit)。

AbstractExecutorService 类解析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jtbh2RNK-1591005776367)(assets/image-20200601102453805.png)]

该类中主要实现了submit方法。

二、ThreadPoolExecutor 使用示例

1、ThreadPoolExecutor 可以通过全参数的构造函数进行实例化:

    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 :线程池拒绝执行任务时候的处理器。

Java 线程池框架提供了以下4种策略

  • AbortPolicy:直接抛出异常。
  • CallerRunsPolicy:只用调用者所在线程来运行任务。
  • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。
  • DiscardPolicy:不处理,丢弃掉。

2、使用示例

public class ThreadPoolDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //
        ExecutorService executorService = new ThreadPoolExecutor(1, 1, 10,
                                                        TimeUnit.SECONDS, new ArrayBlockingQueue<>(16), Executors.defaultThreadFactory());

        executorService.execute(()->{
            System.out.println("12312432");
        });
        Future<String> submit = executorService.submit(() -> {
            Thread.sleep(5000);
            return "11";
        });
        //阻塞获取异步执行的结果
        String result = submit.get();
        System.out.println(result);

        //关闭线程池
        executorService.shutdown();
    }
}

三、ThreadPoolExecutor 源码分析

1、ThreadPoolExecutor 执行的任务的示意图如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0gi4akb-1591005776369)(assets/34e342e0-12f4-11e9-ab44-bbdb99db0ac2)]

1、如果我们运行的线程数少于corePoolSize ,则创建新线程。

2、如果运行的线程等于或者多于corePoolSize ,则将任务添加到阻塞队列中(BlockingQueue )。

3、如果无法将任务放在阻塞队列中,那么就创建额外的线程来执行任务。额外的线程在经历了最大的空闲时间后,将会被销毁。

4、如果创建新的线程池将使用当前运行的线程超出maximumPoolSize ,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution() 方法。

ThreadPoolExecutor采取上述的总体设计思路,是为了在执行execute()方法的时候,尽可能的避免获取全局锁(创建新的线程的时候,需要获取全局锁)。在ThreadPoolExecutor 完成预热后,几乎所有的executor()方法调用都是执行步骤2,而步骤2不需要获取全局锁。

2、execute()方法执行
   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();
		//查看当前的线程是否小于核心线程数,
		//小于则新增一个work线程,并将当前命令作为该线程的第一个任务
		//运行的是上图的第一个分支,创建线程的时候需要全局锁
        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);
        }
		//此时说明运行线程数已经大于核心线程数,且工作队列中已经放满了
		//运行的是分支3
        else if (!addWorker(command, false))
            //没有添加成功,则运行分支4
            reject(command);
3、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.
            //只有在调用shutdown方法采用可能进入
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                //如果是当前运行的线程数大于容量或者  
                //核心线程数(创建的是核心线程取corePoolSize、创建的是非核心线程数,则取maximumPoolSize)
                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
            }
        }

        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //新建worker对象
            w = new Worker(firstTask);
            //获取worker对象的线程对象
            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();
                        //经过上面的检测,后将当前woker对象添加到集合中。
                        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;
    }

通过源码及注解我们可以看出,当我们新增一个工程线程(woker 实例的时候),我们会经历如下主要步骤:

  • 1、使用运行的线程 (wc )< 允许的最大线程数(如果新增核心线程则是最大核心线程数),则添加失败
  • 2、不满足条件1,则会新建使用当前的任务(Runnable实例 )来新建一个woker 实例。
  • 3、获取当前的woker中的线程对象,启用该线程。

从上面的分析,我们可以看出,worker对象是线程池实现的底层核心对象。接下来我们将重点分析该对象

4、Woker 对象分析

Woker 对象的部分源码:

    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;
            //通过初始化ThreadPoolExecutor对象时传入的线程工厂对象来创建一个新的线程对象
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        //woker对象的绑定的线程启动的时候,将会调用runWorker方法。猜测该方法会死循环运行某些方法
       
        public void run() {
            runWorker(this);
        }
        ...
    }

从源码可以看出,wokerRunnable接口的一个实现类。 其绑定的线程对象使用该worker作为任务对象进行初始化的。所以在后续只要我们调用woker 绑定的线程对象的话,就会调用自身实现的run()方法。分析到这里,我们发现runwoker()方法是 Woker 对象的实现任务调用的核心方法。这个对象源码实现回答了开篇的第一个问题。

5、runwoker(Woker woker) 方法分析
 final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //第一个任务存在,或者能够从阻塞的任务队列种获取到任务的时候
            //如果当前的运行的线程数大于核心线程数,超过超时时间后将会返回null。那线程也将会结束
            //这里回答了开篇的第二个问题  
            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 {
                        //任务执行后的方法...  beforeExecute/afterExecute是两个为子类预留的钩子方法
                        afterExecute(task, thrown);
                    }
                } finally {
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //调用shutdown方法后,如果还有那么就进行处理
            processWorkerExit(w, completedAbruptly);
        }
    }

从上面的源码及注释可以看出,代码的逻辑也相对比较好理解。主要第一次任务或者能够从阻塞的任务队列种获取到任务对象,那么该方法将会被一直运行。里面主要完成的任务是:

  • 1、调用钩子方法beforeExecute
  • 2、真正执行任务task.run();
  • 3、调用钩子方法afterExecute

getTask() 对象是该方法种获取任务的核心方法,接下来我们分析它。

6、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?
            //计算当前运行的线程数是否超过最大核心线程数
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

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

            try {
                //超时或者一直阻塞获取任务对象
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
7、submit()方法解析
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        //1、新建了一个RunnableFuture实例  ---> FutureTask
        RunnableFuture<T> ftask = newTaskFor(task);
        //2、调用第二步种的execute方法
        execute(ftask);
        return ftask;
    }

从上述的源码我们可以清楚的看出。submit方法实际上就是将Callable对象封装成一个RunnableFuture 对象。那么这里我们可以肯定的是RunnableFuture 的实例一定是一个Ruannable (因为执行了execute(ftask) )接口,还实现了Future (该方法返回的是Future 对象)接口。查看下RunnableFuture 接口的定义

//
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

然后我们着重看下这个实例对象的run方法实现

//java.util.concurrent.FutureTask#run
public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            //1、获取构造实例方法方法时传入的callable对象
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    //2、执行callable需要执行的任务
                    result = c.call();
                    ran = true;
                } catch (Throwable ex) {
                    result = null;
                    ran = false;
                    //如果发生异常,那么设置相关异常信息
                    setException(ex);
                }
                if (ran)
                    //设置返回结果信息
                    set(result);
            }
        } finally {
            // runner must be non-null until state is settled to
            // prevent concurrent calls to run()
            runner = null;
            // state must be re-read after nulling runner to prevent
            // leaked interrupts
            int s = state;
            if (s >= INTERRUPTING)
                handlePossibleCancellationInterrupt(s);
        }
    }

四、总结

线程是一个应用系统种比较稀缺的资源。如果我们频繁的创建/销毁将会对整个系统产生较大的影响,而线程池的出现,最大程度复用线程对象。使得我们在运行一个异步任务的时候,不需要重新去创建一个新的线程,而在使用完成后也不需立马去销毁。总的来说主要好处有:

  • 1、降低资源消耗。通过复用线程达到
  • 2、提升响应速度。当任务到达时,可直接执行。无需新建线程
  • 3、提升线程的可管理性。线程是稀缺资源,如果无限制地创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配、调优和监控
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值