线程复用的利器:线程池

8 阅读10分钟

什么要使用线程池?

多线程的软件设计⽅法确实可以最⼤限度地发挥现代多核处理器的计算能⼒,提⾼⽣产系统的 吞吐量和性能。但是,若不加控制和管理,随意使⽤线程,则反⽽会对系统的性能产⽣不利的影响

一种最简单的线程创建和回收的实现如下:

        new Thread(new Runnable() {
            @Override
            public void run() {
                
            }
        }).start();

start方法内部调用了本地方法start0创建一个新的线程,在该线程的run方法运行完后会自动回收该线程。

在简单的应⽤系统中,这段代码并没有太多问题,但是在真实的⽣产环境中,系统由于真实环境的需要,可能会开启很多线程来⽀撑其应⽤。当线程数量过多时,反⽽会耗尽CPU内存资源

原因如下:

  1. CPU的角度分析:虽然与进程相⽐,线程是⼀种轻量级的⼯具,但其创建和关闭依然需要陷入内核花费CPU时间。因此当有许多任务的时候,创建了大量的线程去处理这些任务,浪费的资源积累起来就十分地巨大。
  2. 内存的角度分析:线程本身是占有一定的内存空间的,⼤量的线程会抢占宝贵的内存资源,如果处理不当, 则可能会导致Out of Memory异常。即便没有出现异常,⼤量的线程回收也会给GC带来很⼤的压⼒,延⻓GC的停顿时间。

因此为了避免系统频繁地创建和销毁线程,我们可以让创建的线程复⽤。Java提供了一整套的Executor框架帮助开发人员有效地进行线程控 制。其本质就是⼀个线程池。

image.png

ThreadPoolExecutor

线程池状态

private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));  
private static final int COUNT_BITS = Integer.SIZE - 3;  
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;  
  
// runState is stored in the high-order bits  
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;  
private static final int TERMINATED = 3 << COUNT_BITS;

在线程池内部,通过一个32位整型变量同时存储线程池状态线程数量

  1. 高3位存储线程池的状态
  2. 低29位存储线程池中线程的数量

这样通过AtomicInteger可以保证状态数量变更的原子性

线程池的状态如下:

状态值(高3位)描述
RUNNING111正常运行,接受新任务,处理队列任务
SHUTDOWN000关闭,不接受新任务,但处理队列中的任务
STOP001停止,不接受新任务,不处理队列任务,中断进行中的任务
TIDYING010整理中,所有任务已终止,工作线程数为0
TERMINATED011已终止,terminated()方法已执行完成

状态除了RUNNING,其余的状态数值逐渐增大,方便判断。

线程池构造方法

    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;
    }
  1. corePoolSize代表着核心线程数
  2. maximumPoolSize代表着最大线程数
  3. keepAliveTimeunit代表着应急线程在完成任务后的存活时间.
  4. workQueue代表任务阻塞队列
  5. threadFactory代表创建线程的工厂
  6. handler是阻塞队列满了之后的拒绝策略

execute源码分析

	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);
            //如果线程池在运行但没有工作线程(核心线程数为0的情况),创建非核心线程
            else if (workerCountOf(recheck) == 0)		
                //从队列中获取任务执行
                addWorker(null, false);
        }
        //加入队列失败(队列已满),尝试创建非核心线程
        else if (!addWorker(command, false))
            //如果创建非核心线程失败,执行拒绝策略
            reject(command);
    }

总结来说,以上流程如下:

  1. 当前线程数小于核心线程时,创建一个核心线程来处理
  2. 当前线程数大于核心线程时,将任务放入任务队列
  3. 当队列已满时,创建非核心线程执行任务
  4. 当达到最大线程数或者线程池关闭时,执行拒绝策略

addWorker源码分析

其源码如下:

	/**
	*	firstTask:新线程应该首先执行的任务
	*   core:true 表示使用 corePoolSize 作为限制,false 表示使用 maximumPoolSize
	*/
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        //
        for (int c = ctl.get();;) {
            //检查线程池是否允许添加新的worker
            /**当线程池至少处于SHUTDOWN状态时:
            *1.如果线程池是STOP状态,那么绝对不能增加新任务
            *2.SHUTDOWN状态下不允许提交新任务,但可以创建没有初始任务的线程来处理队列中的任务
            *3.SHUTDOWN状态如果队列中没有任务,那么2也没有必要
            */
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP)
                    || firstTask != null
                    || workQueue.isEmpty()))
                return false;
			//检查容量限制
            for (;;) {
                //线程数量超过设定
                if (workerCountOf(c)
                    >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
                    return false;
                //原子性增加线程的数量
                if (compareAndIncrementWorkerCount(c))
                    //跳到下面的逻辑
                    break retry;
                c = ctl.get();
                //如果在这段时间内,线程池的状态变为SHUTDOWN以及其他的状态,跳到外层循坏来重新判断要不要添加任务
                if (runStateAtLeast(c, SHUTDOWN))
                    continue retry;
            }
        }
		/**
		* 能够执行到这里,线程数量已经增加成功
		*/
        boolean workerStarted = false;
        boolean workerAdded = false;
        Worker w = null;
        try {
            //创建一个Worker对象
            w = new Worker(firstTask);
            //获取用于执行任务的线程
            final Thread t = w.thread;
            if (t != null) {
                //加锁
                final ReentrantLock mainLock = this.mainLock;
                mainLock.lock();
                try {
					
                    int c = ctl.get();
					//重新检查状态
                    if (isRunning(c) ||
                        (runStateLessThan(c, STOP) && firstTask == null)) {
                        if (t.getState() != Thread.State.NEW)
                            throw new IllegalThreadStateException();
                        //添加到workers集合
                        workers.add(w);
                        workerAdded = true;
                        // 更新最大池大小记录
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                    }
                } finally {
                    //释放锁
                    mainLock.unlock();
                }
                //worker添加成功后,启动线程
                if (workerAdded) {
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            //如果worker启动失败
            if (! workerStarted)
                //回滚机制
                addWorkerFailed(w);
        }
        return workerStarted;
    }

总结来说其整体流程如下:

  1. 检查线程池的状态:

    • 当线程处于SHUTDOWN时,不允许创建核心线程,只允许创建应急线程处理队列中的任务
    • 当线程处于STOP时,不允许创建任何线程
  2. 检查线程的数量:

    • 核心线程不能大于核心线程数
    • 应急线程+核心线程不能大于总线程数
  3. 创建并启动Worker

    • 创建一个Worker
    • 先加锁保证线程安全
    • 再次检查线程池的状态
    • 添加到worker到集合
    • 启动Worker的线程
  4. 如果启动Worker失败,则回滚

Worker源码分析

Worker是线程池中实际执行任务的工作单元,它:

  • 封装了工作线程 (thread)
  • 维护了要执行的任务 (firstTask)
  • 继承了AQS来实现简单的同步控制
private final class Worker  extends AbstractQueuedSynchronizer  
implements Runnable

Worker本身就是可执行的任务,并且实现了一个简单的不可重入锁。

构造方法

Worker(Runnable firstTask) {
    setState(-1);  // 将state初始化为-1,禁止中断
    this.firstTask = firstTask;
    this.thread = getThreadFactory().newThread(this);
}

Worker中持有线程的引用,而线程内部也会持有Worker的引用。

run方法

Worker本身也是一个Runnable,因此要使用其run方法。

        public void run() {
            runWorker(this);
        }

run方法就是runWorker的一个代理。runWorkerThreadPoolExecutor的方法。其源码如下:


    final void runWorker(Worker w) {
        Thread wt = Thread.currentThread();
        //获取初始任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        //将worker的state从-1提升到0,可以中断了
        w.unlock();
        boolean completedAbruptly = true;
        try {
            //执行任务,要么任务是worker自带的,要么是从队列中获取的
            //结束条件:对于核心线程是线程池关闭,对于应急线程是线程空闲超时
            while (task != null || (task = getTask()) != null) {
                //加锁,将state从0减低到-1,不能中断
                w.lock();
				//当线程池处于STOP状态时,线程被中断
                if ((runStateAtLeast(ctl.get(), STOP) ||
                     (Thread.interrupted() &&
                      runStateAtLeast(ctl.get(), STOP))) &&
                    !wt.isInterrupted())
                    wt.interrupt();
                try {
                    //执行前的钩子
                    beforeExecute(wt, task);
                    try {
                        //运行任务
                        task.run();
                        //执行后的钩子,正常版
                        afterExecute(task, null);
                    } catch (Throwable ex) {
                        //执行后的钩子,异常版
                        afterExecute(task, ex);
                        throw ex;
                    }
                } finally {
                    //任务完成,并解锁
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            //正常退出时:getTask返回null,completedAbruptly=false
            //异常退出时:任务执行异常,completedAbruptly = true
            processWorkerExit(w, completedAbruptly);
        }
    }

总结以上流程如下:

  1. 获取任务:要么是初始任务,要么从任务队列中获取
  2. 执行任务

getTask源码分析


    private Runnable getTask() {
        //判断是否已经超时
        boolean timedOut = false; 
		//
        for (;;) {
            int c = ctl.get();

            //当线程池处于SHUNTDOWN状态且任务队列为空时,或者线程是STOP状态时,不再需要这个线程了,直接扣除线程数,移除线程的逻辑在 processWorkerExit
            if (runStateAtLeast(c, SHUTDOWN)
                && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
                decrementWorkerCount();
                return null;
            }
			//获取当前的线程数
            int wc = workerCountOf(c);

            //决定是否采取超时机制:1.允许核心线程也超时 2.当前线程数大于核心线程
            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
			//当线程数过多||已超时 且 线程数大于||队列为空
            //这个保证至少有一个线程处理任务
            if ((wc > maximumPoolSize || (timed && timedOut))
                && (wc > 1 || workQueue.isEmpty())) {
                if (compareAndDecrementWorkerCount(c))
                    return null;
                continue;
            }

            try {
                //获取任务
                Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }

总结上述流程:

  1. 当线程池处于STOP,或者SHUTDOWN但是队列中已空的情况下,直接减少线程数,并在后续移除线程
  2. 设置超时机制
  3. 获取任务

从这部分代码可以看出,虽说我们在逻辑上区分了核心线程应急线程,但是实际是后续没有任务的线程会认为是应急的,并超时移除。

设核心线程的上限是5,总线程的数量是10,然后一开始会有线程1-线程5为核心线程,后面又增加了线程6-线程10,当队列中没有任务后,可能此时线程3,5,7,9,10在执行任务,而线程1,2,4,6,8等待超时,返回null,最后在processWorkerExit中移除

拒绝策略

当线程池中的任务队列容量已满,还往其中加入任务,或者线程池状态不允许添加新任务的时候,会触发拒绝策略。

默认的拒绝策略有4种:

  1. AbortPolicy中止策略(默认):直接抛出 RejectedExecutionException异常。
  2. CallerRunsPolicy调用者运行策略:将任务回退给调用者线程执行(即提交任务的线程)。
  3. DiscardPolicy丢弃策略:静默丢弃被拒绝的任务,不通知、不抛异常。
  4. DiscardOldestPolicy丢弃最旧策略:丢弃工作队列中等待最久的任务(即队列头部的任务),然后尝试重新提交当前任务。

线程池扩展

ThreadPoolExecutorrunWorker中提供了beforeExecutorafterExecutor两个扩展点。

在实际的应用中,可以对其进行扩展来实现对线程池运行状态的跟踪。

public class ThreadPoolExecutorWithMonitor extends ThreadPoolExecutor {
    public ThreadPoolExecutorWithMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
    }

    public ThreadPoolExecutorWithMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
    }

    public ThreadPoolExecutorWithMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
    }

    public ThreadPoolExecutorWithMonitor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
        super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
    }

    @Override
    protected void beforeExecute(Thread t, Runnable r) {
        System.out.println(t.getName()+"开始执行任务...  "+System.currentTimeMillis()/1000);
    }

    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        System.out.println("任务结束...  "+System.currentTimeMillis()/1000);
    }
}