Java线程池实现原理及线程复用

先来看看线程池的类图(借用了别人的图):

可以看到,Executor是顶层的一个接口,线程池的实现类是ScheduledThreadPoolExecutor和ThreadPoolExecutor,而Executors是一个工具类,用于创建实际的线程池,接下来看看源码。

点进源码查看(是 JDK1.8 版本加入的一种线程池,stealing 翻译为抢断、窃取的意思,它实现的一个线程池和上面4种都不一样,用的是 ForkJoinPool 类),以CachedThreadPool为例:

可以看到实际创建的就是一个ThreadPoolExecutor

 

接下来简单介绍

1.生命周期管理

线程池内部使用一个变量维护两个值:运行状态(runState)和线程数量 (workerCount)。在具体实现中,线程池将运行状态(runState)、线程数量 (workerCount)两个关键参数的维护放在了一起。

它同时包含两部分的信息:线程池的运行状态 (runState) 和线程池内有效线程的数量 (workerCount),高3位保存runState,低29位保存workerCount,两个变量之间互不干扰。用一个变量去存储两个值,可避免在做相关决策时,出现不一致的情况,不必为了维护两者的一致,而占用锁资源。

ThreadPoolExecutor有5种状态:

  • RUNNING 能接受新提交的任务,并且也能处理阻塞队列中的任务
  • SHUTDOWN 关闭状态,不再接受新提交的任务,却可以继续处理保存在阻塞队列中的任务
  • STOP 不能接受新的任务,也不处理队列中的任务,会中断正在处理任务的线程
  • TIDYING 所有的任务都已终止,workCount(有效线程数)为0
  • TERMINATED 在terminated()方法执行完后进入该状态

2.任务执行机制

  1. 首先检测线程池运行状态,如果不是RUNNING,则直接拒绝,线程池要保证在RUNNING的状态下执行任务。
  2. 如果workerCount < corePoolSize,则创建并启动一个线程来执行新提交的任务。
  3. 如果workerCount >= corePoolSize,且线程池内的阻塞队列未满,则将任务添加到该阻塞队列中。
  4. 如果workerCount >= corePoolSize && workerCount < maximumPoolSize,且线程池内的阻塞队列已满,则创建并启动一个线程来执行新提交的任务。
  5. 如果workerCount >= maximumPoolSize,并且线程池内的阻塞队列已满, 则根据拒绝策略来处理该任务, 默认的处理方式是直接抛异常。

见上图CachedThreadPool的创建,可以看到传递了阻塞队列,阻塞队列根据不同的线程池用了不一样的阻塞队列:

如上执行机制提到拒绝策略,当线程池的任务缓存队列已满,并且线程池中的线程数目达到maximumPoolSize时,就需要拒绝掉该任务,采取任务拒绝策略。

拒绝策略是一个接口:

 

简单介绍如何实现线程复用,

逐一方法进行讲解,自然先看看execute方法,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);
    }

这部分代码就是前面提到的线程池执行机制,进行核心线程数或最大线程数的判断等,注意一个addWorker方法,下面截取方法的部分

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();
        }
        if (workerAdded) {
            t.start();
            workerStarted = true;
        }
    }
} finally {
    if (! workerStarted)
        addWorkerFailed(w);
}

将我们传入的runnable对象包装成一个worker对象,接下看看这个worker类

看到worker实现了Runnable接口,自然重写了run()方法,

看到构造方法,根据本对象(Worker)获取到了线程

有两个我框起来的成员属性,分别是Thread和Runnale对象,addWorker()方法中有一句t.start(),线程启动,所以自然会去执行worker中的run()方法,而run()方法又调用了runWorker()方法

看到不断获取任务,如果获取到就进行执行,注意执行是task.run(),也就是说只是对一个Runnable对象里的一个方法的调用,而不是像平常使用线程编程时的start(),直接调用run()方法JVM是不会帮我们去生成新线程的,就像和调用普通方法一样,所以一个线程始终都会在whlie循环的逻辑中不断的被重复利用,然后去取Worker对象的firstTask或者通过getTask方法从工作队列中获取待执行的任务,再直接调用Runnable类型的run方法执行任务。

 

以上是一点对线程池的总结,欢迎大家交流指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值