并发编程 —— 线程池简介和源码分析

线程池简介

线程池的优点:提高性能,提高线程的利用率,可控制线程的最大并发数,方便线程管理。
线程池的核心类是 ThreadPoolExecutor, 可通过 Executors 中的几个方法创建线程池,分别是:

  1. CachedThreadPool
    核心线程数为 0, 线程数量无上限

  2. FixedThreadPool
    核心线程数由使用者自己指定,无非核心线程,线程空闲时不会被回收,除非线程池被关闭

  3. ScheduledThreadPool
    指定数量的核心线程数,线程数无上限,非核心线程空闲时会被回收

  4. SingleThreadExecutor
    核心线程为 1, 无非核心线程,线程空闲时不会被回收。意义在于使得任务都放在同一线程处理,不必考虑线程同步问题。

线程池构造方法参数介绍
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    // ...
}

上面是线程池的构造方法,分析一下创建线程池的七个参数的含义:

  1. corePoolSize
    核心线程数,在空闲时不会被回收,在线程池关闭后才会被回收(allowCoreThreadTimeOut 被设为 true 时会闲置 keepAliveTime 后回收)。

  2. maximunPoolSize
    线程数上限,可利用该参数控制线程最大并发数,当运行的线程超过这个数量时会被阻塞。

  3. keepAliveTime
    非核心线程的闲置时长,超过这个时长就会被回收,当 allowCoreThreadTimeOut 被设为 true 后,这个时长也同样作用于核心线程。

  4. unit
    keepAliveTime 的时间单位。

  5. workQueue
    线程池中的任务队列。

  6. threadFactory
    用于创建线程,它只有一个 newThread(Runnable r) 方法。

  7. handler
    如果执行(添加)任务失败,则会执行 handler.rejectedExecution(command, this); 这个方法在需要处理拒绝后的操作时由我们自己实现来处理。

线程池执行过程

线程池创建过后执行会调用 ThreadPoolExecutor 的 execute(Runnable) 方法来执行线程,步骤如下:

  1. 如果当前线程数小于核心线程,则创建新的线程保存到核心线程并执行;

  2. 否则,核心线程数已满,则插入到任务队列中等待

  3. 插入到任务队列失败,则启动非核心线程来执行

  4. 启动非核心线程失败,则 reject.

源码解析

那么线程池传过去的 Runnable 是怎么运行的,任务队列是怎么排队的,核心线程又是怎么创建和销毁的,从源码中找答案。

Runnabla 是怎么运行的

首先调用 execute 方法,

public void execute(Runnable command) {
    
    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);
    }
    // 添加到非核心线程,失败则 reject
    else if (!addWorker(command, false))
        reject(command);
}

接下来看 addWorker 方法,执行 Runnable 的方法

private boolean addWorker(Runnable firstTask, boolean core) {
    // ... 一些校验条件

    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
    	// 创建 Worker, 内部赋值了 firstTask 并创建了线程,通过 threadFactory.new Thread(worker)
        w = new Worker(firstTask);
        final Thread t = w.thread;
        if (t != null) {
            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                int rs = runStateOf(ctl.get());
                // 对运行状态做校验
                if (rs < SHUTDOWN ||
                    (rs == SHUTDOWN && firstTask == null)) {
                    // ...
                    // 把创建的 worker 对象保存下来
                    workers.add(w);
                    int s = workers.size();
                    if (s > largestPoolSize)
                        largestPoolSize = s;
                    workerAdded = true;
                }
            } finally {
                mainLock.unlock();
            }
            // 成功添加,则开始执行,t.start 会调用 worker.run 方法,因为 newThread 时传的 Runnable 对象是 worker
            if (workerAdded) {
                t.start();
                workerStarted = true;
            }
        }
    } finally {
        if (! workerStarted)
            addWorkerFailed(w);
    }
    return workerStarted;
}

上面分析到 t.start 调的是 worker.run, worker.run 实际执行的是 runWorker(), 下面接着来看这个方法

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask; // 这里的 task 就是 execute 方法传的 Runnable 对象
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        // 循环取 task 执行,第一次 task 不是 null, 第一次循环执行完,下一次就会取 getTask
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // ... 校验
            try {
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    // 这里就是我们调用 execute 方法时实际执行的地方了
                    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();
            }
        }
        completedAbruptly = false;
    } finally {
        processWorkerExit(w, completedAbruptly);
    }
}

上面就看到 Runnable 实际执行的部分了,Runnable 怎么执行的分析就到此结束。

任务队列是怎么排队的呢

上面代码中看到一个 for 循环不断 getTask 就是获取 Runnable 的过程,下面看 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 {
            	// 取任务就是在这里,从 workQueue 中取任务,是通过 workQueue.offer(runnable) 添加的
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
核心线程池又是怎么创建和销毁的

创建核心线程就是在 new Worker 时创建的线程, runWorker方法中 while 循环 getTask 获取 Runnable, getTask 获取不到 Runnable 时会调用 processWorkerExit 来销毁线程,getTask 获取 Runnalble 是通过 workQueue.poll() / workQueue.take().

当创建线程池时设置空闲一定时间就销毁时,就调用 workQueue.poll(), 下面看一下 poll 方法的源码:

public E poll(long timeout, TimeUnit unit) throws InterruptedException {
    E x = null;
    int c = -1;
    // timeout 就是线程闲置这个时间后就需要销毁的
    long nanos = unit.toNanos(timeout);
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        while (count.get() == 0) {
            if (nanos <= 0)
                return null;
            // 如果队列为空,线程就等待 timeout 时间
            nanos = notEmpty.awaitNanos(nanos);
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    if (c == capacity)
        signalNotFull();
    // 所以如果这里在等待 timeout 时间后还没有任务,返回到的就是空
    return x;
}

上面 poll 方法中返回为空时,上面 runWorker 方法中的 while 循环就结束了,那么久会执行 processWorkerExit 方法来中断这个线程,于是就结束执行了这个线程。

设置 allowCoreThreadTimeOut 为 false 时就调用 workQueue.take(), workQueue.take() 方法看一下代码:

public E take() throws InterruptedException {
    E x;
    int c = -1;
    final AtomicInteger count = this.count;
    final ReentrantLock takeLock = this.takeLock;
    takeLock.lockInterruptibly();
    try {
        // 当队列为空时,线程会一直处于等待状态
        while (count.get() == 0) {
            notEmpty.await();
        }
        x = dequeue();
        c = count.getAndDecrement();
        if (c > 1)
            notEmpty.signal();
    } finally {
        takeLock.unlock();
    }
    return x;
}

所以如果核心线程是不可销毁的,那么会调用 workQueue.take(), 在没有任务时线程就会处于等待状态,直到有任务再返回,所以这样情况下核心线程是不会被销毁的。

Stkcd [股票代码] ShortName [股票简称] Accper [统计截止日期] Typrep [报表类型编码] Indcd [行业代码] Indnme [行业名称] Source [公告来源] F060101B [净利润现金净含量] F060101C [净利润现金净含量TTM] F060201B [营业收入现金含量] F060201C [营业收入现金含量TTM] F060301B [营业收入现金净含量] F060301C [营业收入现金净含量TTM] F060401B [营业利润现金净含量] F060401C [营业利润现金净含量TTM] F060901B [筹资活动债权人现金净流量] F060901C [筹资活动债权人现金净流量TTM] F061001B [筹资活动股东现金净流量] F061001C [筹资活动股东现金净流量TTM] F061201B [折旧摊销] F061201C [折旧摊销TTM] F061301B [公司现金流1] F061302B [公司现金流2] F061301C [公司现金流TTM1] F061302C [公司现金流TTM2] F061401B [股权现金流1] F061402B [股权现金流2] F061401C [股权现金流TTM1] F061402C [股权现金流TTM2] F061501B [公司自由现金流(原有)] F061601B [股权自由现金流(原有)] F061701B [全部现金回收率] F061801B [营运指数] F061901B [资本支出与折旧摊销比] F062001B [现金适合比率] F062101B [现金再投资比率] F062201B [现金满足投资比率] F062301B [股权自由现金流] F062401B [企业自由现金流] Indcd1 [行业代码1] Indnme1 [行业名称1] 季度数据,所有沪深北上市公司的 分别包含excel、dta数据文件格式及其说明,便于不同软件工具对数据的分析应用 数据来源:基于上市公司年报及公告数据整理,或相关证券交易所、各部委、省、市数据 数据范围:基于沪深北证上市公司 A股(主板、中小企业板、创业板、科创板等)数据整理计算
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值