Executor & ThreadPoolExecutor 线程池详解

在 Java 中,如果每个请求到达就创建一个新线程,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。如果在一个 Jvm 里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。

为了解决这个问题,就有了线程池的概念,线程池的核心逻辑是提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行就行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。

线程池优势:1.降低创建线程和销毁线程的性能开销

2.提高响应速度,当有新任务需要执行时不需要等待线程创建就可以立马执行

3.合理设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题

1.Executor框架

1)Executor:定义execute()方法执行任务。目的:将任务提交与任务执行解耦。

2)ExecutorService:继承Executor接口,它扩展了Executor接口,定义了更多线程池相关的操作,丰富了对任务执行和管理的功能。

3)AbstractExecutorService:提供了ExecutorService的部分默认实现。

4)ThreadPoolExecutor:实现了线程池工作的完整机制,也是本次分享的重点。

5)ForkJoinPool:实现了Fork/Join模式的线程池。

6)ScheduledExecutorService:扩展了ExecutorService,定义了延迟执行和周期性执行任务的方法。

7)ScheduledThreadPoolExecutor:此接口在继承ThreadPoolExecutor的基础上实现了ScheduledExecutorService接口,提供定时和周期执行任务的特性。

8)Executors:Executor框架提供了Executors对象。是一个工厂及工具类,提供了创建各种不同类型线程池的方法。

为了方便对于线程池的使用,在 Executors 里面提供了几个线程池的工厂方法:

newFixedThreadPool:该方法返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。

newSingleThreadExecutor: 创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。

newCachedThreadPool:返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在 60 秒后自动回收

newScheduledThreadPool: 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。

newWorkStealingPool:JDK1.8 版本加入的一种线程池,stealing 翻译为抢断、窃取的意思,它实现的一个线程池和上面4种都不一样,用的是 ForkJoinPool 类。

1.1 构造ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
    
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
​
​
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.acc = System.getSecurityManager() == null ? null : AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
}

构造参数含义:

1)corePoolSize:线程池的核心线程数量,也就是最小线程数。它的数量决定了添加的任务是开辟新的线程去执行,还是放到workQueue队列里。

2)maximumPoolSize:线程池的最大线程数量

3)keepAliveTime:当前线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内销毁。

4)unit:KeepAliveTime单位

5)workQueue:阻塞队列,用来保存被添加到线程池中但尚未执行的任务,一般有:直接提交队列、有界任务队列、无界任务队列、任务优先队列

6)threadFactory:线程工厂,用来创建线程,一般用默认的。

7)handler:“饱和处理机制”,“拒绝策略”——当任务太多来不及处理时,如何拒绝任务。

举例分析线程池:

public static void main(String[] args) throws InterruptedException {
        Long start = System.currentTimeMillis();
        ExecutorService executorService1 = Executors.newCachedThreadPool();//(缓存线程池)快
        ExecutorService executorService2 = Executors.newFixedThreadPool(10);//(容量固定的线程池)慢
        ExecutorService executorService3 = Executors.newSingleThreadExecutor();//(单线程化线程池)最慢
​
        Stream.iterate(1,item->item+1).limit(200).forEach(item->{
            executorService1.execute(()->{
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " item:"+item);
            });
        });
        executorService1.shutdown();
        executorService1.awaitTermination(1,TimeUnit.HOURS);
​
        Long end = System.currentTimeMillis();
        System.out.println("用时:" + ( end - start ) + "毫秒");
    }
​
​
//返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。
//并且每一个空闲线程会在 60 秒后自动回收
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
​
//该方法返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
​
//创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
​
通过Executors的工厂方法来创建线程,其实根本上是调用ThreadPoolExecutor构造方法时传入的参数不同。

线程池的注意事项

阿里开发手册不建议使用线程池,手册上是说线程池的构建不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。 用 Executors 使得用户不需要关心线程池的参数配置,意味着大家对于线程池的运行规则也会慢慢的忽略。这会导致一个问题,比如我们用 newFixdThreadPool 或者 singleThreadPool.允许的队列长度为Integer.MAX_VALUE,如果使用不当会导致大量请求堆积到队列中导致 OOM 的风险;而 newCachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,也可能会导致大量线程的创建出现 CPU 使用过高或者 OOM 的问题。而如果我们通过 ThreadPoolExecutor 来构造线程池的话,我们势必要了解线程池构造中每个参数的具体含义,使得开发者在配置参数的时候能够更加谨慎。

1.2 启动ThreadPoolExecutor(线程池任务提交)

创建TheradPoolExecutor时并没有启动线程池,何时启动?添加第一个任务的时候,也就是调用executor方法时。

1.2.1 整体结构

首先分析整体结构:

当一个任务被提交到线程池之后:

  • 如果此时线程数小于核心线程数,那么就会新起一个线程来执行当前的任务。

  • 如果此时线程数大于核心线程数,那么就会将任务塞入阻塞队列中,等待被执行。

  • 如果阻塞队列满了,并且此时线程数小于最大线程数,那么会创建新线程来执行当前任务。

  • 如果阻塞队列满了,并且此时线程数大于最大线程数,那么会采取拒绝策略。

需注意:当线程数达到核心数的时候,任务是先入队,而不是先创建最大线程数。

线程池本意只是让核心数量的线程工作着,不论是 core 的取名,还是 keepalive 的设定,所以你可以直接把 core 的数量设为你想要线程池工作的线程数。而任务队列起到一个缓冲的作用。最大线程数这个参数更像是无奈之举,在最坏的情况下做最后的努力,去新建线程去帮助消化任务。

线程池尽可能只维护核心数量的线程,提供任务队列暂存任务,并提供拒绝策略来应对过载的任务。

如果线程数已经达到核心线程数,那么新增加的任务只会往任务队列里面塞,不会直接给予某个线程,如果任务队列也满了,新增最大线程数的线程时,任务是可以直接给予新建的线程执行的,而不是入队

1.2.2 源码分析

1、ctl作用:在线程池中,ctl 贯穿在线程池的整个生命周期中它是一个涵盖了两个概念的原子整数类,主要作用是用来保存线程数量线程池的状态。其高 3 位来保存运行状态runState,低 29 位来保存线程数量workerCount。(64位机器上,一个AtomicInteger对象地址占用8个字节,其内部维护的int值value占用4个字节,也就是32位)

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); 
private static int ctlOf(int rs, int wc) { return rs | wc; }

2、线程池状态

  • RUNNING:能接受新任务,并处理阻塞队列中的任务

  • SHUTDOWN:不接受新任务,但是可以处理阻塞队列中的任务

  • STOP:不接受新任务,并且不处理阻塞队列中的任务,并且还打断正在运行任务的线程,就是直接撂担子不干了!

  • TIDYING:所有任务都终止,并且工作线程也为0,处于关闭之前的状态

  • TERMINATED:已关闭。

  • private static final int COUNT_BITS = Integer.SIZE - 3; //32-3
    //运行状态保存在 int 值的高 3 位 (所有数值左移 29 位)
    private static final int RUNNING = -1 << COUNT_BITS;// 接收新任务,并执行队列中的任务
    private static final int SHUTDOWN = 0 << COUNT_BITS;// 不接收新任务,但是执行队列中的任务
    private static final int STOP = 1 << COUNT_BITS;// 不接收新任务,不执行队列中的任务,中断正在执行中的任务
    private static final int TIDYING = 2 << COUNT_BITS; //所有的任务都已结束,线程数量为 0,处于该状态的线程池即将调用 terminated()方法
    private static final int TERMINATED = 3 << COUNT_BITS;// terminated()方法执行完成

 源码实现:

public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        
        int c = ctl.get();
        //情况一:1、若工作的线程数量小于核心线程数,尝试创建新线程
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))//如果创建成功,则返回
                return;
            c = ctl.get();//重新获取线程池状态,线程池状态可能发生变化
        }
        
        //情况二:工作线程数量大于核心线程数 ,或者新建线程失败  -- 核心池已满,但任务队列未满,添加到队列中
        //2、线程池状态正常,且可以入队的话,尝试入队
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();//获取线程池状态(工作线程数+线程池状态)
            //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了
            if (! isRunning(recheck) && remove(command))//若线程池状态异常,尝试从队列中移除任务,移除成功则拒绝该任务 
                reject(command);
            //如果之前的线程已被销毁完,新建一个线程
            else if (workerCountOf(recheck) == 0)//若发现可运行线程数为0,则初始化一个线程。极限情况下,入队的时候,突然可用线程都被回收了 
                addWorker(null, false);//Runnable是空的,不会影响新增线程,但是线程在start的时候不会运行;Thread.run()里面有判断
        }
        
        //3、核心池已满,队列已满,试着创建一个新线程,但不作为coreThread,若失败则直接拒绝,表明队列满了,且开启线程到maxSize,已饱和。
        else if (!addWorker(command, false))
            reject(command);//拒绝任务
    }

execute()方法中核心流程是addWorker()方法,"结合线程池的情况看是否可以添加新的worker",源码如下:

 //1)参数firstTask:不为空可以直接执行,为空执行不了。Thread.run()方法中有判断;
    //2)参数core:为true表示线程最大新增个数是coreSize,false表示最大新增个数为maxSize
    //3)返回值:true:成功 false:失败
    private boolean addWorker(Runnable firstTask, boolean core) {
        //第一部分作用:校验线程池状态,更新线程数量
        retry://java标记
        for (;;) {//自旋,校验状态
            int c = ctl.get();
            int rs = runStateOf(c);//获取当前线程状态
​
            // Check if queue empty only if necessary.
            //如果线程处于非运行状态,并且 rs 不等于 SHUTDOWN 且 firstTask 不等于空且workQueue 为空,直接返回 false(表示不可添加 work 状态)
            //1. 线程池已经 shutdown 后,还要添加新的任务,拒绝
            //2.(第二个判断)SHUTDOWN 状态不接受新任务,但仍然会执行已经加入任务队列的任务,所以当进入 SHUTDOWN 状态,而传进来的任务为空,并且任务队列不为空的时候,是允许添加
            //新线程的,如果把这个条件取反,就表示不允许添加 worker
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;
        
           
            for (;;) {//自旋
                int wc = workerCountOf(c);//获取线程池中线程数量
                
                //如果超出容量或者最大线程池容量 不再接受新任务
                if (wc >= CAPACITY ||    //如果工作线程数大于默认容量大小或者大于核心线程数大小,则直接返回 false 表示不能再添加 worker。
                    wc >= (core ? corePoolSize : maximumPoolSize)) //core:true代表是往核心线程池中增加线程 false代表往最大线程池中增加线程
                    return false;
                
                if (compareAndIncrementWorkerCount(c))//线程安全的增加工作线程数
                    break retry;//跳出至retry,不再进入循环
                
                c = ctl.get();  // Re-read ctl
                if (runStateOf(c) != rs)//若不等,表明线程池状态改变,则重试
                    continue retry;//跳出至retry,且再次进入循环
                // else CAS failed due to workerCount change; retry inner loop
            }
        }
        
        //至此说明工作线程数增加成功,准备开始正式构建Worker
        
        boolean workerStarted = false;//工作线程是否启动标识
        boolean workerAdded = false;//工作线程是否已经添加成功的标识
        Worker w = null;
        try {
            //新建Worker 
            //初始化时:this.thread = getThreadFactory().newThread(this);
            w = new Worker(firstTask);
            final Thread t = w.thread;//从worker对象中取出线程
            if (t != null) {
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();//加锁,控制对workers的访问权限,避免并发问题
                try {
                    int rs = runStateOf(ctl.get());//获取当前线程池状态
                    //只有当前线程池是正在运行状态(或SHUTDOWN 且 firstTask为空,才能添加到workers集合中)
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) 
                            throw new IllegalThreadStateException();
                        
                        workers.add(w); //将新创建的Worker添加进workers集合中;workers为HashSet<Worker>,包含线程池中所有工作线程
                        
                        int s = workers.size();//获取线程数量
                        if (s > largestPoolSize) //如果集合中的工作线程数大于最大线程数,这个最大线程数标识线程池曾经出现过的最大线程数 
                            largestPoolSize = s;//更新线程池出现过的最大线程数
                        workerAdded = true;//表明线程任务添加成功
                    }
                } finally {
                    mainLock.unlock();//释放锁
                }
                //线程任务添加成功后
                if (workerAdded) {
                    //把worker对象自己作为Runnable的实现传入Thread,那么 addWork() 调用的 t.start(),实际上运行的是t所属 worker的run()方法。
                    t.start();//启动线程,执行的是 Worker的run()方法
                    workerStarted = true;//启动成功
                }
            }
        } finally {
            if (! workerStarted)//若添加失败,则递减实际工作线程数
                addWorkerFailed(w);//移除worker,线程数减1
        }
        return workerStarted;//返回结果
    }

Worker 封装了线程,是 executor 中的工作单元。worker 继承自 AbstractQueuedSynchronizer,并实现 Runnable。

private final class Worker extends AbstractQueuedSynchronizer implements Runnable
    {
        ...
​
        final Thread thread;//从构造函数可知是由ThreadFactory创建的。注意:真正执行task的线程
        Runnable firstTask;//需要执行的task
        volatile long completedTasks;//完成的任务数,用于线程池统计
    
        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker 初始状态-1,防止在调用runWorker(),也就是真正执行task前中断thread.
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }
​
        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
    
        ...
        
    }

Worker 类继承了 AQS,并实现了 Runnable 接口,注意其中的 firstTask 和 thread 属性:firstTask:用它来保存传入的任务;thread 是在调用构造方法时通过ThreadFactory来创建的线程,是用来处理任务的线程。

在调用构造方法时,需要传入任务,这里通过getThreadFactory().newThread(this);来新建一个线程,newThread 方法传入的参数是 this,因为 Worker 本身继承了 Runnable 接口,也就是一个线程,所以一个 Worker 对象在启动的时候会调用 Worker 类中的 run 方法。

ThreadPoolExecutor的核心方法addWorker(),主要作用是增加工作线程,而Worker简单理解其实就是一个线程,里面重写了 run 方法是线程池中执行任务的真正处理逻辑,也就是runWorker()方法,这个方法主要做几件事:

  • 如果 task 不为空,则开始执行 task

  • 如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,则执行该任务

  • 执行完毕后,通过 while 循环继续 getTask()取任务

  • 如果 getTask()取到的任务依然是空,那么整个runWorker()方法执行完毕

final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        //unlock表示当前worker线程允许中断,因为new Worker默认的state=-1,此处是调用Worker类的tryRelease()方法,将state置为0,而interruptIfStarted()中只有state>=0才允许调用中断
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //此处while实现了“线程复用”,如果task为空,则通过getTask来获取任务
            while (task != null || (task = getTask()) != null) {
                w.lock();//加锁,为了在shutdown()时不终止正在运行的worker
                //线程池为stop状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务,所以对于Stop状态以上是要中断线程的。
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())  //!wt.isInterrupted():再一次检查保证线程需要设置中断标志位
                    wt.interrupt();
                try {
                    beforeExecute(wt, task);//这里默认是没有实现的,在一些特定的场景中我们可以自己继承ThreadpoolExecutor自己重写
                    Throwable thrown = null;
                    try {
                        task.run();//执行任务中的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,需要再通过getTask()取)
                    task = null;
                    w.completedTasks++;//记录该worker完成的任务数量
                    w.unlock();//释放锁
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
            //1、将入参worker从数组 workers里删除掉;
            //2、根据布尔值allowCoreThreadTimeOut来决定是否补充新的worker进数组workers
        }
    }

getTask()

worker 线程会从阻塞队列中获取需要执行的任务,这个方法不是简单的take数据,我们来分析下他的源码实现

如何判断线程有多久没有活动?在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做,可以干掉了,看看这个代码片段你就清楚了。

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.
            //对线程池状态的判断,两种情况会 workerCount-1,并且返回 null
            //1. 线程池状态为 shutdown,且 workQueue 为空(反映了 shutdown 状态的线程池还是要执行 workQueue 中剩余的任务的)
            //2. 线程池状态为 stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)
            
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;//返回null,则当前worker线程会退出
            }
​
            int wc = workerCountOf(c);
​
            //timed 变量用于判断是否需要进行超时控制。
            // allowCoreThreadTimeOut 默认是 false,也就是核心线程不允许进行超时;
            // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量;对于超过核心线程数量的这些线程,需要进行超时控制
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
​
            //1.线程数量超过 maximumPoolSize 可能是线程池在运行时被调用了setMaximumPoolSize(),被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize
            //2. timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中,获取任务发生了超时.其实就是体现了空闲线程的存活时间
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))  //目的是控制线程池的有效线程数量
                    return null;
                continue;
            }
​
            try {
                //根据 timed 来判断,如果为 true,则通过阻塞队列poll方法进行超时控制,如果在
                //keepaliveTime 时间内没有获取到任务,则返回 null.
                //否则通过 take 方法阻塞式获取队列中的任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)//如果拿到的任务不为空,则直接返回给 worker 进行处理
                    return r;
                timedOut = true;//如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收
            } catch (InterruptedException retry) {
                timedOut = false;//如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试
            }
        }
    }

这里重要的地方是第二个 if 判断,目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行 execute 方法时,如果当前线程池的线程数量超过了 corePoolSize 且小于maximumPoolSize,并且 workQueue 已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是 timedOut 为 true 的情况,说明 workQueue 已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于 corePoolSize 数量的线程销毁掉,保持线程数量在 corePoolSize 即可。

什么时候会销毁?当然是 runWorker 方法执行完之后,也就是 Worker 中的 run 方法执行完,由 JVM 自动回收。getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环,然后会执行processWorkerExit 方法。

1.2.3 流程图

2.如何合理配置线程池的大小

如何合理配置线程池大小,需要先分析下:

  1. 需要分析线程池执行的任务的特性:CPU密集型还是IO密集型

  2. 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系

  • 如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1

  • 如果是IO密集型,主要是进行IO操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。

一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/ 线程 CPU 时间 )* CPU 数目

这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner测试大量运行次数求出平均值)

3.线程池的关闭

ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

  • shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务

  • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

awaitTermination 连用

awaitTermination的功能如下:

  • 阻塞当前线程,等已提交和已执行的任务都执行完,解除阻塞。

  • 当等待超过设置的时间,检查线程池是否停止,如果停止返回 true,否则返回 false,并解除阻塞。

需要注意的是,awaitTerminationshutdown 执行时都会申请锁,awaitTermination 需要在 shutdown 调用后调用,awaitTermination会在代码中不断检查线程池是否停止(这需要调用 shutdown后等任务全部执行完毕),如果停止,则返回true并释放锁。如果 shutdownawaitTermination后调用的话,在awaitTermination 未超时前,它不会释放锁;而 shutdown 也无法得到锁去让线程池停止。这就形成了死锁。

//线程池相关-shutdown、shoutdownNow、awaitTermination
//shutdown:在 shutdown 调用之前的任务会被执行下去;不会再接受新的任务;如果已经 shutdown 了,再调用不会有其他影响
//shoutdownNow:尝试停止所有正在执行的任务;停止等待任务的处理,并返回等待任务的列表;该方法返回时,这些等待的任务将从队列中清空
//
// 1)shutdown:不会等已提交的任务(在等待队列中的任务)完成执行,让 awaitTermination来实现这个功能
//2)shutdownNow:不会等已执行的任务的完成执行,让 awaitTermination来实现这个功能
​
​
//awaitTermination 的功能如下:
// 阻塞当前线程,等已提交和已执行的任务都执行完,解除阻塞
// 当等待超过设置的时间,检查线程池是否停止,如果停止返回 true,否则返回 false,并解除阻塞
​
//awaitTermination 和 shutdown 执行时都会申请锁,awaitTermination 需要在 shutdown 调用后调用,
// awaitTermination会在代码中不断检查线程池是否停止(这需要调用 shutdown后等任务全部执行完毕),
// 如果停止,则返回true并释放锁。
​
//如果 shutdown 在 awaitTermination后调用的话,在awaitTermination 未超时前,它不会释放锁;
// 而 shutdown 也无法得到锁去让线程池停止。这就形成了死锁。
​
public class ShutDownTest {
    public static void main(String[] args) {
        int count = 10;
        ThreadPoolExecutor tp = new ThreadPoolExecutor(1,1,24, TimeUnit.HOURS,new LinkedBlockingDeque<>(10));
​
        Stream.iterate(1,item->item+1).limit(count).forEach(s->{
​
            try{
                tp.execute(new Task(s));
            }catch (RejectedExecutionException e){
                System.out.println("rejected,task - " +  s);
            }
​
            //shutdown
            //第6个任务之后的任务都被拒绝了,其他任务正常执行。
            //shutdown 方法将线程池状态置为 SHUTDOWN,线程池并不会立即停止,要等正在执行和队列里等待的任务执行完才会停止。
//            if(s == 5)
//                tp.shutdown();
​
​
            //2、shutDownNow
            //调用 shutdownNow 后,第一个任务1正在睡眠的时候,触发了 interrupt 中断,之前等待的任务2-5被从队列中清除并返回,之后的任务被拒绝。
            //通过 interrupt 方法去终止正在运行的任务的,因此无法响应 interrupt 中断的任务可能不会被终止。所以,该方法是无法保证一定能终止任务的。
            //shutdownNow 方法将线程池状态置为 STOP,试图让线程池立刻停止,但不一定能保证立即停止,要等所有正在执行的任务(不能被 interrupt 中断的任务)执行完才能停止。
            if(s==5){
                List<Runnable> tasks = tp.shutdownNow();//尝试停止所有正在执行的任务;停止等待任务的处理,并返回等待任务的列表;该方法返回时,这些等待的任务将从队列中清空
                for (Runnable task: tasks) {
                    if(task instanceof Task){
                        System.out.println("waiting task -- "+((Task) task).getName());
                    }
                }
            }
        });
​
        try {
            tp.awaitTermination(1,TimeUnit.HOURS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
​
        System.out.println("=======================");
        System.out.println("finished");
    }
​
​
    public static class Task implements Runnable{
        String name = "";
        public Task(int i){
            name = "Task-" + i;
        }
        public String getName(){
            return name;
        }
​
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
                System.out.println("sleep completed, "+ getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
//                System.out.println(getName()+ " interrupted");
            }
            System.out.println(getName()+ " finished");
        }
    }
}

  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值