java线程池-理解总结

本文详细分析了Java线程池的工作流程,通过实例代码展示了线程池在任务过多时如何抛出异常并抛弃任务。在Executor方法中,线程池会根据核心线程数、阻塞队列大小和拒绝策略来决定任务的执行。通过源码解析,揭示了线程池在任务执行过程中的线程创建、任务调度以及异常情况的处理。核心线程在特定情况下也会被销毁。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

大意了,温故而知新啊,居然把java线程池的内容给记混了,特写此文章,告诫自己。学习要持之以恒,多思考,多动手。

下面是我自己画的流程图,再次理清思路,告诫自己不要犯错。

一.线程池流程图

二.引入问题 

public class ErrorThreadPoolTest {
    public static void main(String[] args) {

        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 4, 3,
                TimeUnit.SECONDS, new ArrayBlockingQueue<>(10),new MyThreadFactory(),  new ThreadPoolExecutor.AbortPolicy());
        for (int i = 0;i<15;i++){
            pool.execute(new MyWouldErrorRunnable(i));
        }
    }
}

public class MyThreadFactory implements ThreadFactory {
    private AtomicInteger atomicInteger = new AtomicInteger();
    @Override
    public Thread newThread(Runnable r) {
        return new Thread(r,"ctest-"+atomicInteger.incrementAndGet());
    }
}

public class MyWouldErrorRunnable implements Runnable{
    private int i;

    public MyWouldErrorRunnable(int i) {
        this.i = i;
    }

    @Override
    public void run() {
//        System.out.println(Thread.currentThread().getName()+"enter");
//        try {
//            Thread.sleep(1000);
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        }
        //任务很简单,就是打印线程名和i的值
        if(i ==2){
            System.out.println(Thread.currentThread().getName()+"i="+i);
            int k = i/0;
        }else{
            System.out.println(Thread.currentThread().getName()
                    +"i="+i);
        }
//        System.out.println(Thread.currentThread().getName()+"exit");
    }
}

首先,先来分析一下这段代码,线程池的核心线程数是2,最大线程数是4,然后阻塞队列大小是10时;明确一点就是在决绝策略为AbortPolicy的情况下,包含在阻塞队列的任务和正在执行的任务,一共有14个;而我这里启动了15个任务,也就是说在某一时刻,过多任务塞入,由于任务已满,会报错来抛弃任务。

为验证这个代码逻辑,先把int k= i/0;这行代码先注释掉,看看是不是会报出异常。

根据结果,可以看到因为在某一时刻,有超过4+10的任务导致抛出异常来抛弃任务。此时我们关注一个小细节,就是线程的名称都是ctest1,ctest2,ctest3,ctest4,这说明一个问题,此刻线程池从头到尾只存在过4个线程。

好,那么现在我们来思考一个问题?就是如果线程池在执行的过程里面出错,抛异常,那么线程池会怎么处理呢?

 这次我们把int k= i/0;这行代码重新加上并把线程加大变成17保证运行也报抛弃任务异常,运行一下,看看结果是什么样的?

此刻线程名仍然是四个,没有改变。

好,那么接下来就带着问题来看看,源码是怎么做的。

三.原理/源码

1.Executor

1.ThreadPoolExecutor的几个关键参数我就不说了,先来看看executor方法,看看是怎么来执行任务。

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /**
         * 下面有三个步骤
         * 1.如果有少于核心线程数的线程在运行,尝试新建一个线程,并把传进来的线程作为该线程的第一个任务。然后调用
         * addWorker这个原子检查运行状态(RunState)和工作线程数(workerCount),通过返回false去表达我们无法添加线程
         *
         * 2.如果一个任务被成功入队,那么他们需要被双重检查我们是否应该添加线程(因为可能存在有个线程在上次检查后死亡)
         * 或者这个线程已经被关闭自从进入这个方法后。因此我们需要检查状态,去看我们是否需要回滚队列因为有可能状态是已停止;
         * 或者如果那没有线程的话需要新增线程
         *
         * 3.如果我们不能入队,那我们需要尝试去新增线程。如果失败了,我们就能知道我们是关闭了还是饱和了然后去拒绝任务
         */
        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);
    }

 ctl是个原子整型,

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

而RUNNING是-1的左移29位,ctlof是RUNNING 和0的或运算。

因此ctl的初始值1110 0000 0000 0000 0000 0000

而事实上,ctl也是用这个规律去做运算,ctl的高3位用来表示状态,而低29位则是用来标书线程数

private static final int RUNNING    = -1 << COUNT_BITS;       // -1即1111 1111 1111 1111 1111 1111 1111 1111 左移29位后为1110 0000 0000 0000 0000 0000 0000 0000 表示运行状态(高3位)

private static final int SHUTDOWN   =  0 << COUNT_BITS;    //  0即0000  0000 0000 0000 0000 0000 0000 0000 左移29位后为0000 0000  0000 0000 0000 0000 0000 0000表示关闭状态(高3位)
private static final int STOP       =  1 << COUNT_BITS;           //  1即0000  0000 0000 0000 0000 0000 0000 0001 左移29位后为0010 0000 0000 0000 0000 0000 0000 0000表示停止状态(高3位)
private static final int TIDYING    =  2 << COUNT_BITS;         //  2即0000  0000 0000 0000 0000 0000 0000 0010 左移29位后为0100 0000 0000 0000 0000 0000 0000 0000表示关闭状态(高3位)
private static final int TERMINATED =  3 << COUNT_BITS;   //  3即0000  0000 0000 0000 0000 0000 0000 0011 左移29位后为0110 0000 0000 0000 0000 0000 0000 0000 表示关闭状态(高3位)

1.因此在workerCountOf(c)里面计算了工作线程数的数量(由此可见在流程图的第一步),如果工作线程小于核心线程数则就行,添加线程并执行的方法(addWorker);如果成功,则返回,失败则重新计算ctl的值

2.isRunning(c)方法表示判断线程池的工作状态同时尝试加入到队列里面去(在这一步里面,它是先经过了第一步的if的判断;走的这一步时有两种可能,一个是工作线程数大于核心线程数,另一个可能是添加线程的时候失败了),进入判断以后再次检查了线程池的状态,查看状态是否是不是运行状态并从队列里面移除任务,如果返回true则拒绝任务;反之返回false,则查看工作线程数是否是0,如果是则添加线程(判断线程数是否小于最大线程数)。

3.第三步,则是直接尝试添加线程(判断线程数是否小于最大线程数),失败则拒绝任务。

2.addWorker

在之前的Executor方法里面,频繁出现addWorker方法,现在就来看看这个方法做了什么

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);

            // Check if queue empty only if necessary.
            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 {
            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;
    }

 

retry;

这个其实就是一个标志位,叫什么都可以,例子con;也可以。主要作用就是多个循环里面执行continue或者break的时候,把命令交给外层管理。

有兴趣的同学可以网上搜一下。

 好,首先来看一下addWorker方法,可以看到这个方法是个有返回值的方法,然后里面有两个参数firstTask和core

先来看看firstTask的注释:

这个任务是新线程首先需要执行的(除非这个任务为null),工作线程在创建时都会用这个任务初始化。(在executor方法里面),在线程数比核心线程数少的时候都会绕过队列(在这种情况下我们总是新建一个线程);如果队列已经满了,那是创建空闲线程或者替换频死的线程。

core的注释:

如果是true,那就比较的时候和核心线程数比较;false则与最大线程数比较。

 现在我们把思绪放回到源码里面去一步步看做了什么。

1.第一步先做了一个自旋,自旋里面先去查了线程池的运行状态,然后去检查队列是否只在必要条件下为空,必要条件是指线程池运行状态是shundown且firstTask为空的情况下,队列是否为空,为空就退出

  1.1在第一步的自旋里面又加了一个自旋,在这个里面主要检查工作线程数是否比核心线程数大或者最大线程数大,如果比之小则通过cas添加工作线程数然后退出第一步的自旋

2.第二步就是新建一个worker,首先worker是一个继承了AQS和实现了Runnbale的类,new Worker的时候就会用线程池的ThreadFactory的newThread方法创建一个新的线程。然后就使用了一个ReentranLock进行一个加锁操作,把创建的worker添加到workers的HashSetr里面,解锁完之后就调用t的start()方法。那这个start()是怎么执行传进来的firstTask的呢?

来看看Worker内部类的源码

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        /**
         * This class will never be serialized, but we provide a
         * serialVersionUID to suppress a javac warning.
         */
        private static final long serialVersionUID = 6138294804551838833L;

        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;
        /** Initial task to run.  Possibly null. */
        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);
        }

        // Lock methods
        //
        // The value 0 represents the unlocked state.
        // The value 1 represents the locked state.

        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

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

        public void lock()        { acquire(1); }
        public boolean tryLock()  { return tryAcquire(1); }
        public void unlock()      { release(1); }
        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }
    }

 可以看到一点就是他在new的时候,把自身传给了ThreadFactory的newThread方法里面,然后Worker自身有个run方法,里面执行了一个runWorker(this)的方法,接下来就看看runWorker方法做了什么

final void runWorker(Worker w) {
        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);
        }
    }

进入这个方法后,他先获取各个属性,比如firstTask,当前线程,然后就进入到一个循环里面去,如果传进来的任务不为空或者从队列里面拿到的队列不为空,就进入循环。

进入循环后,首先把worker在锁,注意这里加锁的范围只是worker,而不是线程池。然后就判断线程是否中断一番,可以看见在被trycatch包围里面,执行了task的run方法。任务完成或者失败后都会进入finally方法进行解锁和增加完成任务数。

但是这里面在最后的finally方法里面有个processWorkerExit(w, completedAbruptly);方法。那这种方法又干了什么呢?来一起看看代码

3.processWorkerExit

private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

1.如果Worker被打断了,则会减少工作线程数量

2.然后对线程池加锁,给线程池添加worker已完成的任务,最后把worker重workersSet里面移除并解锁。

3.判断当allowCoreThreadTimeOut手动设置为true或者执行的run方法抛出异常,核心线程都会被销毁。但是抛出异常时,会添加新线程,而allowCoreThreadTimeOut手动设置为true时则什么都不做。

因此可以得出结论其实核心线程数也是会被销毁的。

 

今天就先到这里吧。小小总结,未来会不断鞭策自己更加好的去总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值