ThreadPoolExecutor逻辑链梳理 - 5 Worker的Runnable属性

【从问题开始】

在使用线程池的时候往往会看到线程池reject相关日志,例如以下日志:

[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]

遂产生疑问:为什么线程池大小为80,队列长度为3的线程池,只有3个active threads就会触发reject机制?

第四篇分析Worker的AQS属性基本了解到active threads的值取自nactive属性,实际取的是AQS中的state属性非0的,即自身已完成加锁的Worker。本篇继续追溯Worker从创建到加锁的流程,深入了解下它实现Runnable属性的流程。


目录

Thread和run方法

Worker和runWorker方法


Thread和run方法

 Runnable接口提供了唯一的接口方法Run方法

public interface Runnable {

    public abstract void run();
}

Runnable最常用典型的实现就是Thread类。Thread类的核心成员变量包括:

// 线程名称
private volatile String name;
// 线程上下文
ThreadLocal.ThreadLocalMap threadLocals = null;
// 线程组,存储执行线程
private ThreadGroup group;
// 线程执行的可执行单元(线程执行谁)
private Runnable target;

此处不关注Thread的状态(当然Thread状态本身也是非常重要的知识点),仅针对问题的本身,即Thread和run方法的调用。

Thread本身提供了带参构造

public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}



private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize) {
    init(g, target, name, stackSize, null, true);
}


private void init(ThreadGroup g, Runnable target, String name,
                  long stackSize, AccessControlContext acc,
                  boolean inheritThreadLocals) {
    ……
    this.target = target;
    ……
}

可见传入的Runnable对象实际被存入了target属性中

使用过Thread类应该熟悉,新创建的线程通过Thread::start方法启动。

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

可以看到在start方法中,除了一些状态的处理,实际启动的地方即start0方法

private native void start0();

start0是一个native方法,它的实现包括:

  1. 首先,它会检查当前线程对象的状态是否允许启动。如果线程对象的状态不满足启动条件,start0()方法将抛出一个IllegalThreadStateException异常。
  2. 接下来,start0()方法会为新线程分配所需的系统资源和内存空间。这包括为线程创建一个独立的执行环境和堆栈空间。
  3. 一旦系统资源和内存分配完成,start0()方法将调用操作系统提供的原生函数,以便创建一个真正的操作系统级线程。这个原生函数可能是基于平台的API,比如Windows的CreateThread()或Linux的pthread_create()。
  4. 最后,start0()方法会更新线程对象的状态,并且将线程添加到线程调度器中,使其可以被调度执行。

同时,在start0的执行过程中,JVM会自动调用线程thread的run方法。因此在jdk源码中,实际看不到run方法的调用,但其实在start方法触发时就执行了run方法。

看Thread的run方法:

public void run() {
    if (target != null) {
        target.run();
    }
}

本身是非常简单的,即执行target的run方法。看到前面的核心成员属性,target实际上就是一个Runnable对象。既然Thread本身就实现了Runnale,为什么还要有一个Runnable的成员变量呢?

这里应该是为了实现执行线程和可执行任务之间的分离,从而不会让可执行任务被强制绑定在一个执行线程上。如果线程自带run,那么创建多个可执行任务就是多个thread,只能一个执行一个,而通过创建一个Runnable,可以给多个线程执行,对一个Runnable进行并发处理。例如银行取号机,四台取号机共用一天的1-50号码序列,要对一个可执行单元做并发。

Worker和runWorker方法

了解了Thread的的run方法的前世今生,再来看Worker的run方法

首先回顾ThreadPoolExecutor中的addWorker方法的一段代码

// 1.首先执行worker的构造方法
w = new Worker(firstTask);
// 2.取出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();
            workers.add(w);
            int s = workers.size();
            if (s > largestPoolSize)
                largestPoolSize = s;
            workerAdded = true;
        }
    } finally {
        mainLock.unlock();
    }
    if (workerAdded) {
        // 3.执行线程启动执行
        t.start();
        workerStarted = true;
    }
}

首先执行的是worker的构造方法

Worker(Runnable firstTask) {
    setState(-1); // inhibit interrupts until runWorker
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

这里再看Worker的构造方法,就有了一种豁然开朗的感觉,Worker实际上是一个可执行对象的承载对象,实际执行的内容是里面的task,Thread是Worker的执行线程,Worker构造时同时构造一个新Thread,同时将自身传入进去,存入Thread的target变量,同时Thread本身也被存储在Worker的thread变量,做为Worker的执行线程

然后取出Thread t = w.thread,到下面真正执行的地方是t.start()方法,进而调用到thread的run方法

回看上文可知,thread的run方法实际调用的是target的run方法,因此实际调用到了Worker的run方法

public void run() {
    runWorker(this);
}

至此终于来到了Worker中最核心的runWorker方法

方法开始先取出并情况了firstTask变量,即构造时传入的task,执行后就清掉

Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts

然后立即执行执行unlock方法

public void unlock()      { release(1); }

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

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

实际上通过AQS调用到自己重写的tryRelease方法,把自己的AQS状态设置为0,因此,初始化的AQS状态(-1)到0之间的时延实际上就是ThreadPoolExecutor::addWorker方法中Worker初始化到thread.start之间的时延

回到第四篇那个问题:nactive取的是workers中AQS非0的Worker的数量,为什么不会有-1被取到呢,概率应该是非常非常小的(这里为个人推断,欢迎指正与探讨),看addWorker就能看到其原因:

// 1.首先执行worker的构造方法,Worker的AQS状态为-1
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
    final ReentrantLock mainLock = this.mainLock;
    // 2.mainLock加锁,toString方法就不可能再拿到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();
            // 3.worker加入workers,这里才能被统计到
            workers.add(w);
            int s = workers.size();
            if (s > largestPoolSize)
                largestPoolSize = s;
            workerAdded = true;
        }
    } finally {
        mainLock.unlock();
    }
    if (workerAdded) {
        // 4.执行线程启动执行,Worker的AQS状态为0,加上锁变成1
        t.start();
        workerStarted = true;
    }
}

首先Worker构造出来是-1状态,这时还没有加入workers,第二篇中看的toString方法统计nactive时统计不到;接着mainLock加锁,toString方法就势必不会再拿到锁了,因此也不会再执行统计操作;在持锁期间,worker加入workers,然后释放锁,立即执行了start方法,可见worker处于-1状态的大部分时间都在持锁时间段内,被取到-1状态的可能性应该是不会太大的

回到runWorker方法中,选定执行任务

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 = null;
        w.completedTasks++;
        w.unlock();
    }
}

任务的选取首先判定的是传入的task,如果未传入task,通过getTask方法从阻塞队列中取task,后面的代码基本就非常好理解了

另一个比较重要的点就是getTask方法,本身涉及线程池中线程的启停,与AQS的condition产生一定的关联,后续将通过一个新问题引入到这里

本篇结论:

  1. Thread和Worker都是Runnable的实现类,在线程池中Worker的run方法通过addWorker方法在Thread::run方法中启动,而Thread::run方法是通过Thread::start方法通过jvm启动
  2. Worker构造时AQS的state为-1,在runWorker方法中首先解锁成为0,然后加锁成为1,在abortPolicy中输出的是即state非0的数量

至此ThreadPoolExecutor逻辑链梳理章节完成,本系列通过一个线程池的拒绝日志开始:

[Running, pool size = 80, active threads = 3, queued tasks = 3, completed tasks = 1674]

第一篇分析了线程池的构造和execute方法,发现worker是通过addWorker方法的返回值确认是否能过执行的,从而引出了线程池满时的拒绝策略和worker

第二篇分析了线程池的拒绝策略,了解了abortPolicy触发时输出日志是通过toString方法输出的,发现统计对象workers中的worker有一个加锁的流程,nactive实际统计的是加上锁的数量,发现Worker是具备AQS锁属性的

第三篇分析了addWorker方法,了解了一个Worker被传入后的创建流程,以及核心线程池和非核心线程池的概念,发现Worker具备Runnable属性,在run方法中触发了与AQS的交互

第四篇聚焦到Worker,了解了Worker的AQS属性,加解锁逻辑与ReentrantLock进行了对比

本篇同样聚焦到Worker,了解了Worker的Runnable属性,对Worker的run方法启动以及与AQS锁的交互进行了梳理。

  • 24
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值