一、线程池是如何动态调节线程个数

为什么要动态调节线程的个数呢?

比方说我们公司在晚上7点的时候有免费的加班餐,大家可以在App上使用企业支付来免费吃饭,所以会有一个高峰期,这时候就可以把线程池的线程数提高,9点之后再降下来。


如何调节呢?
可以调用线程池的 的 如下方法:

// 设置核心线程大小
executorService.setCorePoolSize(10);
// 设置最大线程大小,为什么要设置这个呢?下面会分析
executorService.setMaximumPoolSize(10);

我们先举个例子看看结果:

ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
            for (int i = 0; i < 15; i++) {
                int a = i;
                Runnable runnable = () -> {
                    Log.e("gggg", "任务" + (a + 1) + "开始");
                    try {
                        Thread.sleep(3_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.e("gggg", "任务" + (a + 1) + "结束");
                };
                executorService.execute(runnable);
            }
            executorService.shutdown();

这是没有动态调节的,我们分析下需要多长时间?
我们 提交 15 个任务,每个任务耗时3秒,核心线程是 2 个,最大线程数 5个,队列大小是10, 注意 ArrayBlockingQueue 是不会扩容的,超过初始容量直接抛出异常。

分析如下:

● 首先 2个核心线程执行2个任务,

● 再次,将10个任务放入任务队列

● 然后,非核心线程3个执行3个任务

即 开始有5个线程执行5个任务,耗时3秒;

任何这5个任务执行完成,那么这5个线程回去任务队列中取5个任务执行,又耗时3秒,此时任务队列中还有5条任务;

等这5个任务执行完成的时候,这5个线程去取任务队列中的最后5个任务,又耗时3秒,可以看出这15个任务共耗时9秒。

我们看看log是不是9秒:

Java线程池FutureTask JAVA线程池动态调整大小_System

从log中可以看出,差不多就是9秒啦。

接下来动态调节了线程数了

ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
            for (int i = 0; i < 15; i++) {
                int a = i;
                Runnable runnable = () -> {
                    Log.e("gggg", "任务" + (a + 1) + "开始");
                    try {
                        Thread.sleep(3_000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Log.e("gggg", "任务" + (a + 1) + "结束");
                };
                executorService.execute(runnable);
            }
            executorService.shutdown();
            // 动态调节线程池中核心线程为10,最大线程为10
            executorService.setMaximumPoolSize(10);
            executorService.setCorePoolSize(10);

同样,我们分析下:

开始我们有5个线程执行5个任务,后来我们增加了5个线程,那么新增加的5个线程就去任务队列中取5条任务执行,3秒后,这10个任务执行完了,然后这10个线程又去任务队列中取任务,此时只有5个线程能取到任务并执行,又耗时3秒,所以总共耗时6秒。

我们看看结果啊:

Java线程池FutureTask JAVA线程池动态调整大小_System_02


可以看到 log上看,总共花了6秒

可以看出动态调节线程大小的效果还是很理想的,这只是个简单的例子,仅供说明,参考。

下面来结合源码分析为什么可行?

先看 setCorePoolSize 方法:

public void setCorePoolSize(int corePoolSize) {
        if (corePoolSize < 0)
            throw new IllegalArgumentException();
         // 设置的核心线程数减去最开始设置的核心线程数   
        int delta = corePoolSize - this.corePoolSize;
        // 重新设置核心线程数
        this.corePoolSize = corePoolSize;
        // 线程池中已有的线程数大于你设置的核心线程数,那么中断空闲的线程任务
        // 这是什么场景呢?就是你高峰期过后,降低线程数,
        // 所以线程池中的已有线程大于你设置的线程数
        if (workerCountOf(ctl.get()) > corePoolSize)
            interruptIdleWorkers();
        else if (delta > 0) { // 如果设置的核心线程数大于之前设置的
            // k 取队列中任务数和delta的最小值,为什么?
            // 因为你调节线程数的目的就是为了添加线程来执行任务,
           // 比方说,你之前核心线程数是2,现在设置成5,那么delta = 3,
           // 如果,此时任务队列中只有2个任务了,那就完全没必要 添加3个线程去执行任务
            int k = Math.min(delta, workQueue.size());
            // 调用 addWorker(null, true),来创建新线程执行任务,addWorker的作用看之前的文章哈
            while (k-- > 0 && addWorker(null, true)) {
            // 判断任务队列中的消息是否为空,空了则跳出循环不再添加线程去执行任务。
                if (workQueue.isEmpty())
                    break;
            }
        }
    }

上面从源码分析了动态调节 核心线程数,那为什么同时要设置最大线程数呢 setMaximumPoolSize? 这个其实想想也就知道了,因为线程池中核心线程不能大于最大线程总数啊。这个逻辑体现在哪里呢?其实就是上面 的 addWorker(null, true)

addWorker 部分代码如下:

private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (; ; ) {
          ......
          ......
        Worker w = null;
        try {
        // new 一个执行任务的 Worker
            w = new Worker(firstTask);
            final Thread t = w.thread;
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                ......
                ......
                if (workerAdded) {
                // 调用线程执行任务
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (!workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }

addWorker 中会 new 一个 Worker 去执行任务,Worker 会调用线程工厂创建一个线程来执行任务,Worker 如下:

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

任务的执行方法 runWorker(Worker w) 的 部分代码如下:

...
  while (task != null || (task = getTask()) != null) {
  ...
  }
  ...

因为我们调节核心线程数的方法是 : addWorker(null, true),即传的是个空任务,所以 runWorker中的 while 循环直接就去任务队列中取任务 getTask 执行,也就是在 getTask 中判断当前线程池的线程数是否大于最大线程数的,如下:

private Runnable getTask() {
        boolean timedOut = false; 

        for (; ; ) {
            int c = ctl.get();
            int rs = runStateOf(c);          
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { 
                decrementWorkerCount();             
                return null;
            }

            int wc = workerCountOf(c);

            // Are workers subject to culling?
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; 

      // wc表示的是线程池中已有的线程数量,如果大于最大线程数maximumPoolSize 
      // 那么 wc 肯定是大于1的(因为maximumPoolSize 至少也是1吧),
     // 那么会 把当前线程池中已有线程数减1,然后返回 null,
     // 即通过动态调节核心线程数来执行任务也就没用了,所以同时要调用设置最大线程数的方法
            if ((wc > maximumPoolSize || (timed && timedOut))
                    && (wc > 1 || workQueue.isEmpty())) {//注3
                if (compareAndDecrementWorkerCount(c))//注4
                    return null;
                continue;
            }         
            try {
                Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        workQueue.take();//注5
                if (r != null)
                    return r;
                timedOut = true;//注6
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

我在上面的注释中解释了,为什么要同时设置最大线程数。 设置最大线程数的方法如下:

public void setMaximumPoolSize(int maximumPoolSize) {
      if (maximumPoolSize <= 0 || maximumPoolSize < corePoolSize)
          throw new IllegalArgumentException();
      this.maximumPoolSize = maximumPoolSize;
      // 线程池中已有的线程数大于最大线程数就中断空闲的线程,这个也是调低最大线程数才会出现
      if (workerCountOf(ctl.get()) > maximumPoolSize)
          interruptIdleWorkers();
  }

好了,动态调节的内容说完了,有问题可以留言哈。下面讲述下 线程池是怎么处理抛出异常任务的线程的

线程抛出异常线程池是如何处理的?会影响其他执行任务的线程吗?线程池是回收还是删除这个线程呢?

我们带着这些疑问,写个demo试试就知道了。代码如下:

ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, 2, 100, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(2));
        Runnable runnable1 = () -> {
            System.out.println("任务1开始  threadNmae = " + Thread.currentThread().getName());
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务1结束 threadNmae = " + Thread.currentThread().getName());
        };

        Runnable runnable2 = () -> {
            System.out.println("异常任务2  start threadNmae = " + Thread.currentThread().getName());
            throw new RuntimeException("我又出bug啦,哈哈哈!!!!");
        };

        Runnable runnable3 = () -> {
            System.out.println("任务3开始  threadNmae = " + Thread.currentThread().getName());
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务3结束 threadNmae = " + Thread.currentThread().getName());
        };
        Runnable runnable4 = () -> {
            System.out.println("任务4开始  threadNmae = " + Thread.currentThread().getName());
            try {
                Thread.sleep(3_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("任务4结束 threadNmae = " + Thread.currentThread().getName());
        };

        executorService.execute(runnable1);
        executorService.execute(runnable2);
        executorService.execute(runnable3);
        executorService.execute(runnable4);
        executorService.shutdown();

我在任务2的时候抛出了一个异常,结果如下:

Java线程池FutureTask JAVA线程池动态调整大小_System_03

从运行结果看来 任务2抛出异常,打印错误堆栈信息,且不会影响其他执行任务的线程。
那么 线程池是怎么处理抛出异常的线程呢? 这个还得从源码中分析:

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            // 如果task你为空,或者去阻塞队列中去取任务不为空,这里的getTask 如果阻塞队列中任务为空 会阻塞当前线程
            // 这里就是线程复用的核心,比方说当这个程执行完当前任务后,就去队列中取任务来执行,这就完成了线程的复用
            while (task != null || (task = getTask()) != null) {
                w.lock();
              
                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) {
                    //  这这里会 catch 住运行时的异常且抛出异常                   
                        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 {
    // 最后会执行到这里,由于是抛出异常了,这里 completedAbruptly = true
            processWorkerExit(w, completedAbruptly);
        }
    }

然后 看看 processWorkerExit(w, completedAbruptly),如下:

private void processWorkerExit(Worker w, boolean completedAbruptly) {

       // 如果是线程执行的任务抛出异常,那么会把线程池中线程数减1
        if (completedAbruptly) {
            System.out.println("任务抛出异常,线程数减1");
            decrementWorkerCount();
        }
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w); // 移除任务
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        int c = ctl.get();
//        System.out.println("processWorkerExit" + " c = " + c);
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && !workQueue.isEmpty())
                    min = 1;
                 // 在核心线程不设置销毁时间时,如果工作线程小于核心线程数的时候那么此条件不成立,执行 下面的addWorker(null, false)来开启新的线程来处理任务
                // 在调用shutDown方法后,会回收核心线程,此时线程池中线程数可能小于核心线程数,或者动态调小核心线程数
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }  
              // 这行代码什么时候会走到呢?1: 线程执行的任务抛出异常
              // 2. 如上面的解释 线程池中的线程小于核心线程
            if (completedAbruptly){
                System.out.println("任务抛出异常, 添加一个执行任务的线程");
                addWorker(null, false);
            }
        }
    }

这上面的代码就分析了,任务抛出异常线程池是如何处理的。