ThreadPoolExecutor原理详细解读二

本文全面解析了ThreadPoolExecutor的工作原理,包括构造器、关闭、状态修改、线程中断、任务队列管理等关键方法。深入探讨了核心线程数、最大线程数、活跃时间的设置与调整,以及如何优雅地关闭线程池。

构造器

其实构造器已经没什么好讲了,知道那些参数就可以了,其实都是调用第三个的,基本能自定义的都可以,比如队列workQueue,线程工厂threadFactory,拒绝策略defaultHandler

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             threadFactory, defaultHandler);
    }
 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

关闭shutdown

这个还是比较优雅的方式,会把所有空闲的线程关闭,然后尝试终止:

 public void shutdown() {//会运行完现有的任务和队列列的任务,不会接受新任务了
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();//做线程的安全验证,不安全会报异常
            advanceRunState(SHUTDOWN);//修改状态
            interruptIdleWorkers();//中断所有空闲线程
            onShutdown(); // hook for ScheduledThreadPoolExecutor //钩子函数
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

修改状态advanceRunState

如果当前状态>=targetState状态,就直接跳出循环了,否则就自旋修改成targetState状态

   private void advanceRunState(int targetState) {
        // assert targetState == SHUTDOWN || targetState == STOP;
        for (;;) {
            int c = ctl.get();
            if (runStateAtLeast(c, targetState) || //已经是TIDYING or TERMINATED 就跳出循环
                ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c))))//修改成功调出循环,否则循环到成功为止
                break;
        }
    }

中断空闲的线程interruptIdleWorkers

可以看到,他会去中断所有空闲的线程,因此其他线程还是会去队列中获取任务执行,直到队列空为止,即可以把任务都完成了然后终止:

   private void interruptIdleWorkers() {
        interruptIdleWorkers(false);//中断所有空闲的
    }
   private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//这里的锁是为了确保不让其他线程对workers有所操作
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {//没有中断过,且可以获得锁,也就是空闲了,不空的不能能获得锁的
                    try {
                        t.interrupt();//中断线程
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)//如果只是中断一个的话就退出
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

停止shutdownNow

这个还是比较粗暴的方式,会把所线程中断,把队列清空,然后尝试终止:

  public List<Runnable> shutdownNow() {//中断所有线程,清空列表
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();//中断所有线程,不管是否空闲
            tasks = drainQueue();//把队列中的任务返回并清空队列
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }

中断所有线程interruptWorkers

活尝试去中断每一个线程:

    private void interruptWorkers() {
        // assert mainLock.isHeldByCurrentThread();
        for (Worker w : workers)
            w.interruptIfStarted();
    }
 	void interruptIfStarted() {
            Thread t;
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

清空队列并返回任务drainQueue

这个就是把workQueue中的任务移除,然后返回,其实就是把没做完的任务给返回来:

    private List<Runnable> drainQueue() {//清空队列
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<>();
        q.drainTo(taskList);//将全部元素移除,并给taskList,这个操作相对高效
        if (!q.isEmpty()) {//如果是DelayQueue或者其他的类型队列可能会一下子没清干净
            for (Runnable r : q.toArray(new Runnable[0])) {//将队列里元素全都转为数组
                if (q.remove(r))//从队列里移除任务
                    taskList.add(r);//将任务加入taskList
            }
        }
        return taskList;
    }

超时等待终止awaitTermination(long timeout, TimeUnit unit)

这个就是用了条件终止,会进行阻塞等待终止,如果超时就返回false:

    public boolean awaitTermination(long timeout, TimeUnit unit)
        throws InterruptedException {
        long nanos = unit.toNanos(timeout);
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            while (runStateLessThan(ctl.get(), TERMINATED)) {
                if (nanos <= 0L)
                    return false;
                nanos = termination.awaitNanos(nanos);//超时条件同步
            }
            return true;
        } finally {
            mainLock.unlock();
        }
    }

设置核心线程数setCorePoolSize

重新设置核心线程数还可能需要进行空闲线程的中断,如果发现设置的核心线程数变多了,如果发现等待队列里有任务,还需要增加核心线程,否则就不增加核心线程了:

 public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0 || maximumPoolSize < corePoolSize)
            throw new IllegalArgumentException();//小于0,或者比max大,就非法异常
        int delta = corePoolSize - this.corePoolSize;//核心线程数量差
        this.corePoolSize = corePoolSize;
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();//如果发现现有的线程个数大于核心线程数,就中断空闲的线程
        else if (delta > 0) {//如果新的核心线程数多余老的
            int k = Math.min(delta, workQueue.size());//从核心线程数和等待任务数中取最小的 作为k 下面要增加核心线程数了
            while (k-- > 0 && addWorker(null, true)) {//如果k>0,且新开启线程成功,就循环开启,直到k为0。要么就是任务没了退出,要么线程数已经扩充到最新的核心线程数了
                if (workQueue.isEmpty())//如果任务为空,那就跳出
                    break;//其实就是创建不超过核心线程数的线程来解决队列里的任务,如果队列里空了,就不增加线程了
            }
        }
    }

预启动核心线程prestartCoreThread

预启动一个核心线程,不做任何事,等于预加载:

 public boolean prestartCoreThread() {
        return workerCountOf(ctl.get()) < corePoolSize &&//线程数小于核心线程数
            addWorker(null, true);//且增加核心线程成功
    }

确保预启动成功ensurePrestart

怕没启动成功,如果核心线程启动失败,就启动非核心线程:

  void ensurePrestart() {//确保有一个线程启动
        int wc = workerCountOf(ctl.get());
        if (wc < corePoolSize)
            addWorker(null, true);//没超过核心线程数
        else if (wc == 0)
            addWorker(null, false);//启动一个非核心的
    }

预启动所有核心线程prestartAllCoreThreads

只要能创建的,都创建了:

  public int prestartAllCoreThreads() {
        int n = 0;
        while (addWorker(null, true))//启动所有的核心线程,没任务也没关系,这样可以提高效率,不至于来了再创建线程
            ++n;
        return n;
    }

允许核心线程超时allowCoreThreadTimeOut

核心线程也是可以设置超时的,如果从false设置成了true,会中断空闲线程:

public void allowCoreThreadTimeOut(boolean value) {
        if (value && keepAliveTime <= 0)//如果keepAliveTime<=0,且核心线程要超时回收就报异常
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        if (value != allowCoreThreadTimeOut) {//设置标志
            allowCoreThreadTimeOut = value;
            if (value)
                interruptIdleWorkers();//如果是要收回,立即中断空闲线程
        }
    }

设置最大线程数setMaximumPoolSize

修改最大线程数,如果小于原来的,就要释放空闲线程了:

  public void setMaximumPoolSize(int maximumPoolSize) {
        if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
            throw new IllegalArgumentException();//不合理的抛出异常
        this.maximumPoolSize = maximumPoolSize;
        if (workerCountOf(ctl.get()) > maximumPoolSize)
            interruptIdleWorkers();//如果当前线程数量已经大于最大的了,就中断空闲的
    }

设置活跃时间setKeepAliveTime

如果设置的时间已经过期的话,就直接中断空闲的了:

 public void setKeepAliveTime(long time, TimeUnit unit) {
        if (time < 0)
            throw new IllegalArgumentException();
        if (time == 0 && allowsCoreThreadTimeOut())//核心线程不能设置0过期
            throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
        long keepAliveTime = unit.toNanos(time);
        long delta = keepAliveTime - this.keepAliveTime;
        this.keepAliveTime = keepAliveTime;
        if (delta < 0)//已经过期了就中断空闲线程
            interruptIdleWorkers();
    }

从队列里删除任务remove

可见,如果有任务删除,就会去尝试终止,毕竟可能没任务了,所以要尝试下:

 public boolean remove(Runnable task) {
        boolean removed = workQueue.remove(task);
        tryTerminate(); // In case SHUTDOWN and now empty
        return removed;
    }

清除所有取消的任务purge

可以清除workQueue里所有取消的任务,如果发现异常就把剩下的转成数组,一个个清除:

 public void purge() {//清除所有取消的任务
        final BlockingQueue<Runnable> q = workQueue;
        try {
            Iterator<Runnable> it = q.iterator();
            while (it.hasNext()) {
                Runnable r = it.next();
                if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
                    it.remove();//如果设置了取消就删除
            }
        } catch (ConcurrentModificationException fallThrough) {
            // Take slow path if we encounter interference during traversal.
            // Make copy for traversal and call remove for cancelled entries.
            // The slow path is more likely to be O(N*N).
            for (Object r : q.toArray())//如果遇到异常的话,就把剩下来的队伍转到成数组,一个个移除
                if (r instanceof Future<?> && ((Future<?>)r).isCancelled())
                    q.remove(r);
        }

        tryTerminate(); // In case SHUTDOWN and now empty
    }

疑惑解答

基本的方法基本都分析过了,再说几个比较疑惑的点。

tryTerminate到处有

我们可以看到tryTerminate很多地方都有,为什么会这样呢:
在这里插入图片描述
我们可以从调用这个方法的地方看到,基本都是一些跟workQueue删除相关的,因为可能删除后,队列里没任务了,所以这个时候可以尝试去终止。

mainLock到处有

我们发现这个mainLock也很多地方有,这个是一个ReentrantLock类型的锁:
在这里插入图片描述
从上面的方法里可以分析出,这个锁主要是对ctlworkerslargestPoolSizecompletedTaskCount等一些共享变量的操作进行互斥。另外就是对一些中断操作进行同步。

为什么用阻塞队列放任务

因为我的线程是要不停的执行任务,如果任务获取为空就停止了:
在这里插入图片描述
这样会导致一段时间内如果任务为空,大部分线程被回收,后面又来了高并发,又创建很多,这样对性能消耗比较大,所以应该是用阻塞的比较好,反正没有任务就阻塞好了,总比浪费CPU或者频繁创建消耗好,而且稳定。

为什么没用到synchronized

因为如果出现很多线程竞争资源的话synchronized可能会变成重量级锁,效率就低了,而用ReentrantLock来控制比较灵活而且效率相对高一点,还能超时中断。

为什么线程是线程工程创建的

可以进行进行自定义,而且让线程池的更加专注于管理和调度线程,不用管他的创建。

shutdownNow到底怎么中断的

我们可以看到如果调用shutdownNow,他会让线程中断:
在这里插入图片描述
但是这个好像没地方看到有中断响应啊,这个只是设置了标志位,线程还是会运行啊。确实是这样,如果没有对任务进行中断处理,就等于没中断啦,比如:

 public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            while (true)
                System.out.println("完成");

        });
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdownNow();
    }

你会发现这样没用,他还是会无限输出,就跟shutdown()一样啦,因为没有响应中断啊。
所以你要这样在任务里处理中断:

   public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("完成");
            }

        });
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdownNow();
    }

有中断就立即停掉了。当然也可以跑出中断异常,比如你用sleep

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值