Java并发编程|第十一篇:线程池使用及源码分析

Java线程池

文章目录

  • Java线程池
    • 1.线程池的使用场景
    • 2.Java中的线程池
      • 线程池的核心参数
      • Executors类API
        • FixedThreadPool分析
        • CachedThreadPool
    • 3.源码分析
      • ThreadPoolExecutor#execute
      • ctl 核心变量
      • 线程池状态变化
      • addWorker()
      • 线程复用
      • Worker
      • state状态
      • addWorkerFailed()
      • tryTerminate()
      • interruptIdleWorkers()
      • runWorker()
      • getTask()
      • processWorkerExit()
      • reject()
      • 线程池关闭
        • shutdown()
        • shutdownNow()
    • 4.线程池监控
      • 测试Demo
    • 5.非核心Worker工作线程创建如何回收?
    • 6.参考

1.线程池的使用场景

一般使用线程的方式 new Thread().start();

这样有两个问题:

  1. 线程的创建数量不可控,比如下面的例子中size这个值可能非常大

     for (int i = 0; i < size ; i++) {
          new Thread().start();
     }
    
  2. 频繁的创建和销毁线程

线程池如何解决这些问题:

  1. 控制线程的数量,采用核心线程数,最大线程数,以及阻塞队列的设计
  2. 降低频繁的创建和销毁线程开销,线程的复用
  3. 使得对应任务的响应速度更快

2.Java中的线程池

线程池的核心参数

public ThreadPoolExecutor(int corePoolSize,//核心线程数
                          int maximumPoolSize,//最大线程数
                          long keepAliveTime,//超时时间,超出核心线程数以外的线程空余存活时间
                          TimeUnit unit,//超时时间单位
                          BlockingQueue<Runnable> workQueue,//执行任务线程的阻塞队列
                          ThreadFactory threadFactory,//线程工厂
                          RejectedExecutionHandler handler//任务无法执行的拒绝策略
                          ) {...

线程池初始化时是没有创建线程的,线程池的线程初始化与其他线程一样,但是在完成任务之后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。
这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销。

Executors类API

Java JUC给我们提供了一些已经封装好的线程池工具,屏蔽了很多细节,使用更加方便。

但是同时也是一把双刃剑,使用太多之后可能会忘记线程池的核心参数(所以在阿里巴巴的开发手册中不建议使用此类)

  • newFixedThreadPool

    • new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
      
    • 核心线程数和最大线程数相等 nThreads

    • 固定线程数量的线程池

    • 当有一个任务提交时,如果线程池中的线程空闲,则立即执行;如果没有会被暂缓在任务队列中等待

  • newCachedThreadPool

    • new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>())
      
    • 根据实际情况调整线程个数的线程池,核心线程数为 0,但不限制最大线程数量

    • 如果有空闲的线程则执行任务,没有任务不创建线程。并且每个空闲线程会在60秒后自动回收

  • newSingleThreadExecutor

    • new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>())
      
    • 创建一个核心线程的线程池,在空闲时执行,如果没有空闲线程,多余线程在任务队列中等待

  • newScheduledThreadPool

    • 指定线程的个数,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器
  • newWorkStealingPool

    • fork/join,这个是 JDK1.8 版本加入的一种线程池,stealing 翻译为抢断、窃取的意思,它实现的一个线程池和上面 4 种都不一样,用的是 ForkJoinPool 类
    • 最明显的用意就是它是一个并行的线程池,参数中传入的是一个线程并发的数量,这里和之前就有很明显的区别,前面 4 种线程池都有核心线程数、最大线程数等等,而这就使用了一个并发线程数解决问题。从介绍中,还说明这个线程池不会保证任务的顺序执行,也就是 WorkStealing 的意思,抢占式的工作

FixedThreadPool分析

public class ThreadPoolDemo implements Runnable {

    static ExecutorService executorService = Executors.newFixedThreadPool(3);

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            executorService.execute(new ThreadPoolDemo());
        }
    }

    @Override
    public void run() {
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        String name = Thread.currentThread().getName();
        System.out.println(name);
    }
}

执行结果
Java并发编程|第十一篇:线程池使用及源码分析_第1张图片

通过代码的执行效果可以看到,线程一直只有 thread-1/thread-2/thread-3 三个线程在执行

通过现象看本质

  • FixedThreadPool 的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。

  • keepAliveTime 为 0L,超出核心线程数量以外的线程空余存活时间,保证只有核心线程存在

  • 阻塞队列是LinkedBlockingQueue,默认的容量是Integer.MAX_VALUE,相当于没有上限

此线程池执行任务的流程如下:

  1. 线程少于核心线程数,新建线程执行任务

  2. 线程数大于核心线程数,将任务加入到阻塞队列

  3. 阻塞队列没有限制,可以一直添加

  4. 线程执行完当前任务,空闲时会从队列中获取任务执行

CachedThreadPool

它的执行流程如下:

  1. 没有核心线程,直接向 SynchronousQueue 阻塞队列中提交任务

    SynchronousQueue 没有容量,是无缓冲等待队列,不存储元素的阻塞队列, 每一个 put 操作必须等待一个 take 操作,否则不能继续添加元素。

  2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个

  3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收

3.源码分析

ThreadPoolExecutor#execute

执行逻辑图:
Java并发编程|第十一篇:线程池使用及源码分析_第2张图片

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);
        }
        //核心线程池已满,队列已满,尝试创建一个非核心新的线程
        else if (!addWorker(command, false))
            //如果创建新线程失败,说明线程池关闭或者线程池满了,拒绝任务
            reject(command);
    }

ctl 核心变量

ctl 是一个原子类,主要作用是用来保存线程数量和线程池的状态。

一个int类型是32bit位,高3位表示线程池的状态,低29位表示线程数量。

以 RUNNING 为例

-1 二进制 = 1111 1111 1111 1111 1111 1111 1111 1111

左移29位 = 1110 0000 0000 0000 0000 0000 0000 0000
Java并发编程|第十一篇:线程池使用及源码分析_第3张图片
通过runStateOf()方法计算获得状态

通过workerCountOf()方法计算获得线程数量

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;//32-3 = 29
    //将1左移29位,再-1表示最大线程容量
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits 运行状态保存在高3位
    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()方法执行完成

    private static int runStateOf(int c)     { return c & ~CAPACITY; }//获取当前状态
    private static int workerCountOf(int c)  { return c & CAPACITY; }//获取当前线程数
    private static int ctlOf(int rs, int wc) { return rs | wc; }//当wc为0时得到的还是原值

线程池状态变化

Java并发编程|第十一篇:线程池使用及源码分析_第4张图片

addWorker()

  • 第一段代码主要是通过自旋的方式添加工作线程数
  • 第二段代码是创建工作线程
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //第一段代码增加工作线程数
        for (;;) {
            int c = ctl.get();
            int rs = runStateOf(c);//获取当前状态

            // 1.状态>=SHUTDOWN 只有 4中状态 SHUTDOWN STOP TIDYING TERMINATED 都表示线程池将要关闭,不允许增加worker
            // 2.SHUTDOWN状态不接受新任务,但仍然会执行已经加入任务队列的任务,所以当进入SHUTDOWN状态,而传进来的任务为空,并且任务队列不为空的时候,是允许添加新线程的,如果把这个条件取反,就表示不允许添加worker
            if (rs >= SHUTDOWN &&
                ! (rs == SHUTDOWN &&
                   firstTask == null &&
                   ! workQueue.isEmpty()))
                return false;

            for (;;) {//通过自旋的方式添加工作线程数
                int wc = workerCountOf(c);//当前工作线程数
                //判断当前工作线程数>=最大线程数 或者 >=核心线程数(当core = true)
                if (wc >= CAPACITY ||
                    wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))//cas增加工作线程数
                    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 {
            //firstTask 是我们传入的实现了runnable接口的对象实例
            w = new Worker(firstTask);//创建一个新的worker
            final Thread t = w.thread;//从 worker 对象中取出线程
            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());//获取当前状态

                    //rs
                    //SHUTDOWN状态且firstTask为空
                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // 如果工作线程还存活,理论上不可能存活
                            throw new IllegalThreadStateException();
                        workers.add(w);//否则添加workers HashSet集合中
                        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 自循环线程 一直执行(当然也有退出条件)
  • new App()作为task传入到Worker中,Worker中会通过工厂模式创建一个Thread
  • t.start() - > Worker执行runWorker方法,最后调用task.run()执行方法
  • 如果三个Worker都在执行任务,其他任务会放到workQueue阻塞队列中
  • 当三个核心线程执行完,会通过getTask()方法从workQueue阻塞队列中获取要执行的任务继续执行
  • 一直如此循环,直到 task = getTask() == null
  • 通过上述的方式只使用三个核心自循环线程就可以执行完100个任务,减少了线程频繁创建和销毁
    Java并发编程|第十一篇:线程池使用及源码分析_第5张图片

Worker

1.Worker 实现了 Runnable 接口,每个 Worker 其实都是一条线程,同时里面包含了一个 firstTask,即初始化时要被首先执行的任务

2.Worker 中有一个thread属性,在构造方法初始化的时候,通过工厂类创建赋值,addWroker()中的t.start()最终执行的是Worker.runWorker()方法

3.Worker 继承了 AQS,使用 AQS 来实现独占锁的功能。为什么不使用 ReentrantLock 来实现呢?可以看到 tryAcquire 方法,它是不允许重入的,而 ReentrantLock 是允许重入的;lock 方法一旦获取了独占锁,表示当前线程正在执行任务中;那么它会有以下几个作用

  1. 如果正在执行任务,则不应该中断线程

  2. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断

  3. 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态

  4. 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程

    private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
    {
        private static final long serialVersionUID = 6138294804551838833L;
        /** Thread this worker is running in.  Null if factory fails. */
        final Thread thread;//这个线程才是真正执行task的线程
        /** Initial task to run.  Possibly null. */
        Runnable firstTask;//这就是需要执行的task
        /** Per-thread task counter */
        volatile long completedTasks;//完成的任务数,用于线程池统计

        Worker(Runnable firstTask) {
            setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行task前中断 thread
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);//创建线程
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }
        
        protected boolean isHeldExclusively() {
            return getState() != 0;
        }

        protected boolean tryAcquire(int unused) {//竞争锁
            if (compareAndSetState(0, 1)) {//cas设置状态为1,表示获得锁,设置OwnerThread=当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        protected boolean tryRelease(int unused) {//释放锁
            setExclusiveOwnerThread(null);//设置OwnerThread=null
            setState(0);//设置状态为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) {
                }
            }
        }
    }

state状态

state值 意义
-1 初始状态
0 无锁
1 获得锁

addWorkerFailed()

如果AddWorker失败了,回滚之前添加worker的操作

1.如果worker不为空,从wokers hashSet集合中移除这个worker

2.cas的方式工作线程数-1

3.尝试结束线程池

    private void addWorkerFailed(Worker w) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();//加锁
        try {
            if (w != null)//如果work不为空 表示添加成功
                workers.remove(w);//删除当前work
            decrementWorkerCount();//工作线程数-1
            tryTerminate();//尝试结束线程池
        } finally {
            mainLock.unlock();//释放锁
        }
    }

tryTerminate()

尝试结束线程池

    final void tryTerminate() {
        for (;;) {//自旋
            int c = ctl.get();
            //1.如果状态为运行状态
            //2.或者状态为TIDYING
            //3.或者状态为SHUTDOWN 并且 阻塞队列不为空
            if (isRunning(c) ||
                runStateAtLeast(c, TIDYING) ||
                (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
                return;//不关闭线程池
            if (workerCountOf(c) != 0) { // 工作线程数不为0
                interruptIdleWorkers(ONLY_ONE);//中断线程
                return;//不关闭线程池
            }

            final ReentrantLock mainLock = this.mainLock;
            mainLock.lock();
            try {
                if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {//cas设置ctl的状态为TIDYING
                    try {
                        terminated();//空方法
                    } finally {
                        ctl.set(ctlOf(TERMINATED, 0));//设置ctl的状态为TERMINATED
                        termination.signalAll();//唤醒所有被termination.await阻塞的线程
                    }
                    return;
                }
            } finally {
                mainLock.unlock();
            }
            // else retry on failed CAS
        }
    }

interruptIdleWorkers()

中断空闲的worker线程

    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {//循环workers集合
                Thread t = w.thread;//获取worker中的t线程
                if (!t.isInterrupted() && w.tryLock()) {//尝试获取lock
                    try {
                        t.interrupt();//中断线程
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();//释放锁 这个锁在runWorker方法中获取
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

runWorker()

1.自循环线程

2.通过getTask()方法不断从阻塞队列中获得任务,通过task.run()方法执行

3.while 循环执行完毕以后,在 finally 中会调用 processWorkerExit,来销毁工作线程

    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // 释放锁 设置work的state=0 允许中断
        boolean completedAbruptly = true;
        try {
            //一直执行 如果task不为空 或者 从队列中获取的task不为空
            while (task != null || (task = getTask()) != null) {
                w.lock(); //上锁,不是为了防止并发执行任务,为了在shutdown()时不终止正在运行的 worker线程
                //线程池为stop状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务
                //确保线程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志
                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();//执行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,需要再通过 getTask()取)    
                    task = null;
                    w.completedTasks++;//记录该 Worker 完成任务数量
                    w.unlock();//解锁
                }
            }
            completedAbruptly = false;
        } finally {
            //1.将 worker 从数组 workers 里删除掉
            //2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers
            processWorkerExit(w, completedAbruptly);
        }
    }

getTask()

1.从阻塞队列workQueue中拿数据
2.非核心线程释放

    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {//自旋
            int c = ctl.get();
            int rs = runStateOf(c);//获取线程池状态

            // 单线程池关闭时,阻塞队列是否为空
            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                decrementWorkerCount();//工作线程数-1
                return null;//跳出上一层runWorker的循环
            }

            int wc = workerCountOf(c);//获取当前工作线程数

            // 1.allowCoreThreadTimeOut 设置为true,表示核心线程数也会被回收
            // 2.或者当前线程数 > 核心线程数
            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))//cas设置当前线程数-1
                    return null;//返回上一层 runWorker方法中while()退出条件,getTask()=null
                continue;
            }

            try {
                //如果timed为true,通过poll来控制超时时间获取阻塞队列任务,如果超时则r=null
                //否则通过take来获取阻塞队列中的任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)//如果不为空,返回给runWorker方法继续处理
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

processWorkerExit()

1.将 worker 从数组 workers 里删除掉
2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 workerHashSet数组 workers

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        //一般情况下completedAbruptly为false 机runworker正常执行并退出 异常情况下 true
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();//工作线程数-1

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;//用做线程池的监控 
            workers.remove(w);//将 worker 从数组 workers 里删除掉
        } finally {
            mainLock.unlock();
        }

        tryTerminate();//尝试结束线程池

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {//如果线程池还未到STOP状态
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())//阻塞队列不为空
                    min = 1;
                if (workerCountOf(c) >= min)//工作线程数>核心线程数  或者 大于1
                    return; // replacement not needed
            }
            addWorker(null, false);//增加一个空的非核心线程,如果还有任务没有执行,会在此工作线程执行
        }
    }

接着回到ThreadPoolExecutor#execute,上面主要分析的是addWorker()增加核心工作线程和非核心工作线程,Worker类如何实现线程的复用,runWorker()如何执行任务。接下来我们看看当最大线程数和阻塞队列都满了,线程池的拒绝策略。

reject()

四种拒绝策略实现类

1.AbortPolicy: 直接抛出异常,默认策略。

2.CallerRunsPolicy:用调用者所在的线程来执行任务。

3.DiscardOldestPolicy:丢弃阻塞队列中最靠前的任务,并执行当前任务。

4.DiscardPolicy:直接丢弃任务。

这四个实现都比较简单,就不贴出源码进行分析了。

Java并发编程|第十一篇:线程池使用及源码分析_第6张图片

线程池关闭

ThreadPoolExecutor 提供了两个方法,用于线程池的关闭

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

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

shutdown()

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);//设置线程池的状态
            interruptIdleWorkers();//中断空闲工作线程,中断前会进行一些判断
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }

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;//返还阻塞队列中还存在的任务
    }

4.线程池监控

如果在项目中大规模的使用了线程池,那么必须要有一套监控体系,来指导当前线程池的状态,当出现问题的时候可以快速定位到问题;通过重写 shutdown()beforeExecute()afterExecute() 等方法可以实现对线程的监控。

public class ThreadPoolMonitor extends ThreadPoolExecutor {

    private ConcurrentHashMap<String, Date> startTimes;

    public ThreadPoolMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
        this.startTimes = new ConcurrentHashMap<>();
    }

    @Override
    public void shutdown() {
        System.out.printf("已经执行的任务数:[%s]\n当前活动的线程数:[%s]\n当前排队的任务数:[%s]", this.getCompletedTaskCount(), this.getActiveCount(), this.getQueue().size());
        super.shutdown();
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        startTimes.put(String.valueOf(r.hashCode()),new Date());
        super.beforeExecute(t, r);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        Date startDate = startTimes.remove(String.valueOf(r.hashCode()));
        Date finishDate = new Date();
        long diff = finishDate.getTime() - startDate.getTime();
        // 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、
        // 已完成任务数量、任务总数、队列里缓存的任务数量、
        // 池中存在的最大线程数、最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止
        System.out.print("任务耗时:"+diff+"\n");
        System.out.print("初始线程数:"+this.getPoolSize()+"\n");
        System.out.print("核心线程数:"+this.getCorePoolSize()+"\n");
        System.out.print("正在执行的任务数量:"+this.getActiveCount()+"\n");
        System.out.print("已经执行的任务数:"+this.getCompletedTaskCount()+"\n");
        System.out.print("任务总数:"+this.getTaskCount()+"\n");
        System.out.print("最大允许的线程数:"+this.getMaximumPoolSize()+"\n");
        System.out.print("线程空闲时间:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS)+"\n");
        super.afterExecute(r, t);
    }

    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolMonitor(3, 5, 0L, TimeUnit.SECONDS, new
                LinkedBlockingQueue<> ());
    }
}

测试Demo

public class ThreadPoolMonitorDemo implements Runnable {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService executorService = ThreadPoolMonitor.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
            executorService.execute(new ThreadPoolMonitorDemo());
        }
        executorService.shutdown();
    }

    @Override
    public void run() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

执行结果:
Java并发编程|第十一篇:线程池使用及源码分析_第7张图片

5.非核心Worker工作线程创建如何回收?

getTask() 方法中的这一段

1.满足条件 wc > corePoolSize 此时工作线程数 > 核心线程数timed = true

2.那么r = workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)通过poll获取阻塞队列的任务,如果在 keepAliveTime 时间内获取到任务,执行任务

3.如果没有获取到任务,返回null,退出runWorker的自循环线程,执行完线程,JVM进行回收

           // 满足条件 wc > corePoolSize 此时工作线程数>核心线程数 timed = true
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
            ... 
            try {
                //那么poll从阻塞队列中获取任务,超时返回null
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;

6.参考

腾讯课堂->咕泡学院->mic老师->并发编程基础

你可能感兴趣的:(Java并发编程)