面试官必看的线程池核心细节

前言

不管是工具类还是复杂的框架,其底层的东西都是可以细化,量化,甚至是形象化地表述出来,只有深入了解其最终的缓存模型,运行流程,才算是真正学到精髓!今天再来深挖一下线程池,搞明白下面几个问题:

  • 线程池最核心的实例缓存对象分别是哪些?
  • 线程池的线程是如何创建,保存的?
  • 线程池是如何扫描对象并运行任务的?

一、线程池状态

为方便看源码,先补充说明一下线程池的状态设计,了解状态设计对看源码有很多帮助;

Java线程池状态设计的很巧妙,其中高3位用来表示线程池状态,后29位用来表示线程数量;因为最高位是符号位,因此线程池的状态最多有7种[-3 ~ 3],实际上只用到了5种,如下:

private static final int COUNT_BITS = Integer.SIZE - 3;  // 29
// runState is stored in the high-order bits
private static final int RUNNING    = -1 << COUNT_BITS;  // -536870912
private static final int SHUTDOWN   =  0 << COUNT_BITS;  // 0
private static final int STOP       =  1 << COUNT_BITS;  // 536870912
private static final int TIDYING    =  2 << COUNT_BITS;  // 1073741824
private static final int TERMINATED =  3 << COUNT_BITS;  // 1610612736

如图:高3位是状态位,低29位是线程数量; 

int占4个字节,共32比特,数据范围:[-2^31~2^31-1] (-2147483648 ~ 2147483647)

二、核心实例对象

1、线程集合:HashSet<Worker> workers

池中用于容纳全部工作线程的Set集合。我们知道线程类是Thread,这个Worker就是包装了Thread的内部类,从下面源码中可以找到Thread实例属性;

/**
 * Set containing all worker threads in pool. Accessed only when
 * holding mainLock.
 */
private final HashSet<Worker> workers = new HashSet<Worker>();

private final class Worker extends AbstractQueuedSynchronizer implements Runnable {
    /** Thread this worker is running in.  Null if factory fails. */
    final Thread thread;

    /** Initial task to run.  Possibly null. */
    Runnable firstTask;

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

        // 生成线程,关键是this参数
        this.thread = getThreadFactory().newThread(this); 
    }
}

这个Worker本身就是一个任务类,因为它继承了Runnable接口,它与线程对象相互引用,在线程启动后,就会调用Worker类的run方法,因此,必须要清楚线程池的两种任务:

  • 普通任务,放在阻塞队列,负责处理业务逻辑的任务;
  • 线程任务,放在Worker集合,负责扫描阻塞队列,拉取普通任务并执行;

——这一点搞懂了,才算是深入了解线程池原理;

除此之外,Worker类中还有个firstTask对象,也是个任务对象,它的作用是在创建Worker的时候,并没有把业务任务放阻塞队列,而是直接交给Worker来执行,这个firstTask就是负责承接初创时的业务任务;

2、阻塞队列:BlockingQueue<Runnable> workQueue

下图是任务的提交流程,阻塞队列提供暂存业务逻辑任务的功能;当然,不是所有的任务都会入阻塞队列,要根据线程池的运行情况和运行状态决定是否入队列;

三、线程池运行流程

先简单写个使用线程池的例子:

public class ThreadPoolTest {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(10);
        service.submit(() -> System.out.println("a"));
    }
}

当我们submit一个任务的时候,任务就会被自动执行,那么线程池是如何运行的呢?

跟踪submit方法,在AbstractExecutorService抽象类里面:

    public Future<?> submit(Runnable task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<Void> ftask = newTaskFor(task, null);
        execute(ftask);
        return ftask;
    }

继续跟踪execute:

    public void execute(Runnable command) {
        // 多余代码先不看,重点就在这个 addWorker 中
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
    }

comand是普通任务,下面代码可以看到,在Worder类的构造方法中创建了线程对象,而线程对象的目标任务又是this(看上面2.1章节:ThreadPoolExecutor.Worker内部类的构造方法),也就是上面提到的相互依赖;当新创建Worker的时候,不会把普通任务塞给阻塞队列,而是直接复值firstTask!

    private boolean addWorker(Runnable firstTask, boolean core) {
        // 同样省略部分代码
        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());

                    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);
        }
        return workerStarted;
    }

重点来了,上面提到两遍Worker和线程的关系,Worker继承了Runnable接口,那么当启动线程的时候,就会去调用Worker的run方法

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

最终调用到runWorker,这个是线程池的核心运行流程,但是也很简单,run Worker本质上就是执行任务,如果Worker是首次创建的,那么也同时赋值了一个普通任务,直接执行即可,如果不是首次创建的Worker,则需要从阻塞队列中捞取任务;

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    // 如果是首次创建的Worker,那么firstTask有值,就是普通任务
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 进入循环,两个条件:
        // 1. firstTask非空,就不要从阻塞队列获取,直接运行firstTask
        // 2. firstTask为空,需要从阻塞队列获取任务
        while (task != null || (task = getTask()) != null) {
            w.lock();
            try {
                // 这是个空方法,用于自定义线程池的时候,自定义处理逻辑
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 运行任务
                    task.run();
                } catch (RuntimeException x) {
                    thrown = x; throw x;
                } finally {
                    afterExecute(task, thrown); // 同beforeExecute一样
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

来看捞取任务方法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判断也很重要,如果调用了shutdown/shutdownNow方法,那么“核心线程”也会返回null
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c); // 取线程数

        // Are workers subject to culling?
        // allowCoreThreadTimeOut = false
        // timed = true 表示存在非核心线程
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        // wc > maximumPoolSize = false 一般不会出现这种情况,所以是false
        // 这个if条件成立的前提:必须存在非核心线程【关键】
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null; // 这个null就是非核心线程被销毁的罪魁祸首
            continue;
        }

        try {
            // 获取任务,但要看当前线程数量是否超过核心线程数,
            // 如果超过了,则使用pool方法,否则使用take方法
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r; // 能取到任务就返回,否则设置timedOut进去死循环
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

take:如果队列中没有数据,则线程wait释放CPU;

poll:如果没有数据返回,直接返回null; 

    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0) {
                if (nanos <= 0)
                    return null;
                nanos = notEmpty.awaitNanos(nanos);
            }
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

四、总结

1、线程池提供一个Set集合属性worker用来缓存所谓的线程(其实是线程的一个包装);

2、线程池提供一个workQueue队列用来保存任务;

3、线程包装类Worker也是一个任务类,线程通过Worker不停地执行任务并扫描队列;

4、通过阻塞队列的poll和take方法获取任务,以此让非核心线程在取不到任务后消亡,让核心线程在取不到任务后等待;——这种说法是为了便于理解,实际上并没有严格区分核心与非核心线程,仅仅是以线程数量判断

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值