一文看懂Java线程池原理

首先看下线程池的类ThreadPoolExecutor,了解下相关属性

java 复制代码
// 使用原子变量记录线程池状态和线程数
// 高三位是线程池状态,低29位是当前工作线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static final int COUNT_BITS = Integer.SIZE - 3;
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

// runState is stored in the high-order bits
// 0b11100000_00000000_00000000_00000000
private static final int RUNNING    = -1 << COUNT_BITS;
// 0b00000000
private static final int SHUTDOWN   =  0 << COUNT_BITS;
// 0b00100000_00000000_00000000_00000000
private static final int STOP       =  1 << COUNT_BITS;
// 0b01000000_00000000_00000000_00000000
private static final int TIDYING    =  2 << COUNT_BITS;
// 0b01100000_00000000_00000000_00000000
private static final int TERMINATED =  3 << COUNT_BITS;
//核心线程数,当工作线程数小于corePoolSize直接创建线程执行任务
private volatile int corePoolSize; 
//最大线程数,当前工作线程大于corePoolSize且工作队列满时且小于maximumPoolSize,创建线程执行任务
private volatile int maximumPoolSize;
// 无法创建线程时的处理
private volatile RejectedExecutionHandler handler;
// 创建线程的工厂
private volatile ThreadFactory threadFactory;
// 线程创建出来后的存活时间
private volatile long keepAliveTime;
// 存储创建线程的集合
private final HashSet<Worker> workers = new HashSet<Worker>();
// 存储创建任务的集合
private final BlockingQueue<Runnable> workQueue;

创建线程池,构造方法为

java 复制代码
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler){...}

看下最后一个参数RejectedExecutionHandler handler,这是个接口表示当任务无法执行时的拒绝策略,主要有四种:

  1. 在当前线程中运行
  2. 抛出异常
  3. 啥都不做,即丢弃
  4. 抛弃等待时间最长的
java 复制代码
public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
public static class CallerRunsPolicy implements RejectedExecutionHandler {
    // 在当前线程中运行
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

public static class AbortPolicy implements RejectedExecutionHandler {
    // 抛出异常
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                                " rejected from " +
                                                e.toString());
    }
}

public static class DiscardPolicy implements RejectedExecutionHandler {
    // 啥都不做,即丢弃
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    // 抛弃等待时间最长的
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

线程池的核心方法就是execute(),直接分析这个方法就行了:

  1. 如果工作线程数小于核心线程数,创建线程执行任务
  2. 如果工作线程数大于核心线程数,线程池在运行,队列没有满,添加到队列
  3. 如果工作线程数大于核心线程数,线程池在运行,队列已满,但是工作线程小于最大线程,则继续创建线程执行任务
java 复制代码
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        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);
        }
        // 如果工作线程数大于核心线程数,线程池在运行,队列已满,但是工作线程小于最大线程,则继续创建线程执行任务
        // 否则使用拒绝策略RejectedExecutionHandler
        else if (!addWorker(command, false))
            reject(command);
    }

执行任务时调用的addWorker(),再来分析这个方法,本质就是生成Worker保存到内置的队列中。

java 复制代码
// firstTask表示线程创建时执行的任务,如果null则表示要到队列中取任务
// core为true表示是否为跟核心线程数corePoolSize,false表示跟maximumPoolSize比较
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        // 这段代码做的事情就是判断是否可以创建新的线程执行任务
        for (;;) {
            // 获取线程池运行状态
            int c = ctl.get();
            int rs = runStateOf(c);

            // 如果线程池不在运行了的处理
            // 当线程没有运行时,有新的任务来不作处理,firstTask == null表示没有新的任务
            // 当线程没有运行时,没有新的任务,队列为空不作处理,workQueue.isEmpty()表示任务队列为空
            // 当线程池为SHUTDOWN时,线程池只会处理队列中现有的任务
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {
                int wc = workerCountOf(c);
                // 是否可以创建线程,如果工作线程数比最大线程数还大时就不能创建线程了
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                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是实现Runnable接口的类
            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实例对象,然后加入线程池,再看下这个类即可

java 复制代码
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
       
        private static final long serialVersionUID = 6138294804551838833L;
// 保持线程的实例对象
        final Thread thread;
// 要执行的任务
        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;
            // 使用工厂方法创建线程
            this.thread = getThreadFactory().newThread(this);
        }

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

可以看到执行任务的核心为Worker类的runWorker()方法,继续分析:首先获取工作线程,执行execute方法接受的Runable任务,然后到队列中获取等待的任务。

java 复制代码
    final void runWorker(Worker w) {
        //获取工作线程,即调用start()方法的线程
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        //这段代码是说先执行自身任务,再到队列中获取任务执行
        try {
            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();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

从队列中获取任务,判断核心线程是否能被销毁,默认不能被销毁,不被销毁时会调用队列的take()方法一直存活,可见核心线程也有cpu占用,如果是能被销毁的核心线程或最大线程,则调用poll()方法存活keepAliveTime时间后被销毁。

java 复制代码
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // 如果线程池SHUTDOWN了则无法获取任务
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }

            int wc = workerCountOf(c);

            // 这里就是说核心线程是不会被销毁的,看看怎么实现的
            // 默认allowCoreThreadTimeOut的值是false,如果工作线程数超过核心线程则是true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            // 工作线程数超过最大线程数或者队列为空都不能获取任务
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                // 销毁线程的动作在这里
                // 如果工作线程数大于核心线程数,则最多存活keepAliveTime,poll的等待时间为keepAliveTime
                // 如果工作线程数小于核心线程数,则永远不会不会销毁,take不会退出的
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

看下怎么销毁线程的吧,关键在poll()和take()的使用。

java 复制代码
    public E poll(long timeout, TimeUnit unit) throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        // 可以看到,当队列长度count为0时,最多等待timeout时间
        // 那么等待这么长时间后,线程就会退出run()方法,被销毁掉
        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();
        // 这里是没有设置时间的,当队列为空时会一直等待,因此线程会一直在run()方法中,不会被销毁
        try {
            while (count == 0)
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }

补充:execute()方法只能接收Runnable作为参数,而submit方法既能接收Runnable又能接收Callable作为参数。

当调用submit()提交Callable任务时,会对FutureTask实例化,而FutureTask实现了Runnable接口中的run()方法,可以看到run方法其实就是调用Callable接口的call()方法,并且获取call()的返回值保存下来。

java 复制代码
// AbstractExecutorService.java
    public <T> Future<T> submit(Callable<T> task) {
        if (task == null) throw new NullPointerException();
        RunnableFuture<T> ftask = newTaskFor(task);
        execute(ftask);
        return ftask;
    }
    // RunnableFuture
    //实现了Runnable接口的run方法
    public void run() {
        if (state != NEW ||
            !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                         null, Thread.currentThread()))
            return;
        try {
            Callable<V> c = callable;
            if (c != null && state == NEW) {
                V result;
                boolean ran;
                try {
                    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);
        }
    }

当计算完成时就会将计算结果保存到outcomeprivate Object outcome;

java 复制代码
    protected void set(V v) {
        if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
            outcome = v;
            UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
            finishCompletion();
        }
    }

Future的get其实就是获取run()中保存的result并返回。

java 复制代码
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }
    private V report(int s) throws ExecutionException {
        Object x = outcome;
        if (s == NORMAL)
            return (V)x;
        if (s >= CANCELLED)
            throw new CancellationException();
        throw new ExecutionException((Throwable)x);
    }

线程池的参数设置注意事项:

  1. 队列的长度最好不要声明的太大,要有个长度限制,不然会oom
  2. 如果是CPU密集型设置corePoolSize = CPU核数 + 1,IO密集型设置corePoolSize = CPU核数 * 2

欢迎大家一起讨论交流,共同进步,更多请关注微信公众号 葡萄开源

相关推荐
coding随想3 分钟前
网络层的“四骑士”:深入浅出IP、ICMP、ARP、RARP协议
后端·网络协议
sino爱学习3 分钟前
基于Redis 发布订阅实现一个轻量级本地缓存刷新
后端
bug菌16 分钟前
还在为编程效率发愁?字节跳动Trae如何让你秒变“代码大师“!
后端·ai编程·trae
Moonbit21 分钟前
MoonBit Perals Vol.04: 用MoonBit 探索协同式编程
后端·程序员·编程语言
2501_9096867021 分钟前
基于SpringBoot的旅游网站系统
vue.js·spring boot·后端
HZ_YZ23 分钟前
服务器docker部署项目
后端
用户849210736938036 分钟前
Skywalking 部署
后端
bug菌37 分钟前
🤔领导突然考我Spring中的注解@Bean,它是做什么用的?我...
java·后端·spring
aiopencode1 小时前
移动端网页调试实战,触摸事件穿透与点击冲突问题的定位与优化
后端
菜鸟Debug1 小时前
🚀 Redisson 分布式锁源码解析:从加锁到解锁的完整流程
后端