线程池工作原理

一、线程和进程的区别

一个线程只能归属于一个进程;

一个进程至少拥有一个线程。

线程池工作流程

创建一个线程池,核心线程数为2,最大线程数为5,非核心线程的空闲等待时间是10s, 等待队列使用ArrayBlockingQueue,饱和策略是AbortPolicy。

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
corePoolSize:核心线程数。核心线程默认不超时。
maximumPoolSize:最大线程数。
keepAliveTime:非核心线程的空闲等待时间。临时线程执行完任务后,会主动去任务队列里获取任务。如果经过 keepAliveTime 没有获取到,临时线程会被销毁。
workQueue:等待队列。
handler:拒绝策略、饱和策略。

线程数与CPU核数的关系:最大线程数与CPU可同时处理的线程数相同。例如:4核CPU,8个逻辑处理器。那么设置maximumPoolSize=8, corePoolSize = 4.

提交一个任务到线程池中,线程池的处理流程如下:

  1. 判断线程池里的核心线程是否都在执行任务,如果不是(核心线程空闲或者还有核心线程没有被创建),则创建一个新的工作线程来执行任务,否则进入下一个流程。
  2. 线程池判断工作队列是否已满,如果工作队列没有满,则将新提交的任务存储在工作队列里。如果工作队列满了,则进入下一个流程。
  3. 判断线程池的线程是否处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给饱和策略来处理这个任务。

     

三、线程池状态之间的转换

状态

含义

RUNNING

线程池的初始化状态是RUNNING, 线程池处在RUNNING状态时,能够接收新任务,以及对已添加的任务进行处理

SHUTDOWN

线程池处在SHUTDOWN状态时,不接收新任务,但能处理已添加的任务,异步中断闲置的的线程,调用线程池的 shutdown() 接口时,线程池由RUNNING -> SHUTDOWN

STOP

线程池处在STOP状态时,不接收新任务,不处理已添加的任务,并且会中断正在处理的任务。用线程池的 shutdownNow() 接口时,线程池由 (RUNNING or SHUTDOWN ) -> STOP

TIDYING

当所有的任务已终止,ctl记录的”任务数量”为0,线程池会变为TIDYING状态。当线程池变为TIDYING状态时,会执行钩子函数terminated()。terminated()在ThreadPoolExecutor类中是空的,若用户想在线程池变为TIDYING时,进行相应的处理,可以通过重载terminated()函数来实现。   当线程池在SHUTDOWN状态下,阻塞队列为空并且线程池中执行的任务也为空时,就会由 SHUTDOWN -> TIDYING。  当线程池在STOP状态下,线程池中执行的任务为空时,就会由STOP -> TIDYING

TERMINATED

线程池彻底终止,就变成TERMINATED状态。 线程池处在TIDYING状态时,执行完 terminated()之后,就会由 TIDYING -> TERMINATED

 

线程池的三种队列

  1. SynchronousQueue
    1. SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者,必须等待队列中的添加元素被消费后才能继续添加新的元素。
    2. 使用SynchronousQueue阻塞队列一般要求maximumPoolSize为无界,避免线程拒绝执行操作。  
  2. LinkedBlockingQueue
    1. LinkedBlokingQueue是一个无界缓存等待队列,当前执行的线程数量达到corePoolSize数量时,剩余的元素会在阻塞队列里等待,(所以在使用此阻塞队列时maximumPoolSize就相当于无效了),每个线程完全独立于其他线程。生产者和消费者使用独立的锁来控制数据的同步,即在高并发的情况下可以并行操作队列中的数据。
  3. ArrayBlockingQueue
    1. ArrayBlockingQueue是一个有界缓存等待队列,可以指定缓存队列的大小。当正在执行的线程数等于corePoolSize时,多余的元素缓存在ArrayBlockingQueue队列中,等待有空闲的线程时继续执行。当ArrayBlockingQueue已满时,加入ArrayBlockingQueue失败,会开启新的线程去执行,当线程数已经达到最大的maximumPoolSize时,再有新的元素尝试加入ArrayBlockingQueue时会报错。

 

五、饱和策略

  1. AbortPolicy:抛出异常,丢弃任务。
  2. DiscardPolicy: 不抛出异常,丢弃最新的任务。
  3. DiscardOldestPolicy: 不抛出异常,丢弃旧的未执行的任务。
  4. CallerRunsPolicy: 不丢弃任务,调用线程池的线程(caller)帮忙执行任务。

六、Executors线程池的工具类(不推荐使用工具类)

Executors线程池的工具类提供了4种快捷创建线程池的办法。

  1. newCachedTreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,如果无可回收,则新建线程。可复用线程。
    //核心线程数为0,不限制最大线程数。
    public
    static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); }

     

  2. newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
    //指定线程数。核心线程数=最大线程数。 无空闲线程
    public
    static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); }

     

  3. newSingleTreadPool:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
    //核心线程数 = 最大线程数 = 1,无空闲时间
    public
    static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>())); }

     

  4. newScheduledTreadPool:创建一个定长线程池,支持定时及周期性任务执行。
            //创建一个Scheduled线程池,核心线程数是2
            ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
            //延迟执行。延迟时间4s
            scheduledExecutorService.schedule(new MyRunnable("name"), 4, TimeUnit.SECONDS);
            //延迟并周期性执行。延迟4s执行,之后每5s执行一次。
            scheduledExecutorService.scheduleAtFixedRate(new MyRunnable("name2"), 4, 5, TimeUnit.SECONDS);

     

七、线程池的体系结构

java.util.concurrent.Executor    线程池的顶级接口,定义了线程池的最基本方法
      java.util.concurrent.ExecutorService       定义常用方法
            java.util.concurrent.ThreadPoolExecutor     线程池的核心实现类
                   java.util.concurrent.ScheduledThreadPoolExecutor              
java.util.concurrent.Executors    线程池的工具类

 

 

八、线程池的作用分析

通过创建固定数量的线程来执行大量的任务,这样可以很好的复用线程,减少线程的创建和维护的时间消耗。

 

九、源码分析

名词解释:ctl

ctl 是一个打包两个概念字段的原子整数。

1)workerCount:指示线程的有效数量;

2)runState:指示线程池的运行状态,有 RUNNING、SHUTDOWN、STOP、TIDYING、TERMINATED 等状态。

int 类型有32位,其中 ctl 的低29为用于表示 workerCount,高3位用于表示 runState,如下图所示。

 

复制代码
    /**
     * 线程池控制状态ctl包含2个概念字段:workerCount,指有效的线程数量;runState,指线程池的状态。
     * 为了将workerCount和runState用1个int来表示,我们限制workerCount范围为(2^29 - 1),
     * 即用int的低29位用来表示workerCount,用Int的高3位来标识runState。
     *
     * 初始化时有效线程数为0,此时ctl为:1010 0000 0000 0000 0000 0000 0000 0000
     */
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //高3位用来表示运行状态,此值用于运行状态向左移动的位数,即29位
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //线程数容量。低29位表示有效的线程数,0001 1111 1111 1111 1111 1111 1111 1111
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;

    /**
     * 大小关系:RUNNING < SHUTDOWN < STOP < TIDYING < TERMINATED
     * 源码中频繁使用大小关系作为条件判断
     * 1010 0000 0000 0000 0000 0000 0000 0000 运行
     * 0000 0000 0000 0000 0000 0000 0000 0000 关闭
     * 0010 0000 0000 0000 0000 0000 0000 0000 停止
     * 0100 0000 0000 0000 0000 0000 0000 0000 整理
     * 0110 0000 0000 0000 0000 0000 0000 0000 终止
     */
    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;//终止

    /**
     * 得到运行状态。入参为ctl的值
     * ~CAPACITY的高3位全是1,低29位全是0,
     * 因此运算结果为ctl的高3位
     */
    private static int runStateOf(int c)     { return c & ~CAPACITY; }

    /**
     * 得到有效线程数.入参为ctl的值
     * CAPACITY的高3位全是0,低29位全是1,
     * 因此运算结果为ctl的低29位
     */
    private static int workerCountOf(int c)  { return c & CAPACITY; }
复制代码

 CTL这么设计有什么优点?

  • 主要好处是将对runState和workerCount的操作封装成一个原子操作。
  • runState和workerCount是线程池正常运转的两个最重要属性。线程池在某一时刻该做什么,取决于这两个字段的值。
  • 因此无论是查询还是修改,我们必须保证对这2个属性的操作是属于“同一时刻”的,也就是原子操作,否则就会出现错乱的情况。如果我们使用2个变量来分别存储,要保证原子性则需要额外进行加锁操作,这显然会带来额外的开销。而将这两个变量封装成一个AutomaticInteger,则不会带来额外的加锁开销,而且只需使用简单的位操作就能分别得到runState和workerCount。

 

复制代码

/**
     * 存放池中的通过线程. 只有获取到主锁才能访问
     */
    private final HashSet<Worker> workers = new HashSet<Worker>();



//
执行任务 public void execute(Runnable command) { if (command == null) throw new NullPointerException(); //主要作用:1、记录线程池的状态信息。2、记录线程池工作线程的数量 int c = ctl.get(); //workerCountOf(c) 工作中线程的数量 //流程1:如果当前工作中的线程数小于核心线程数,则新建一个核心工作线程 if (workerCountOf(c) < corePoolSize) { //添加一个Worker。参数1:要执行的任务。 参数2:true:添加核心线程。false:添加临时线程 if (addWorker(command, true)) return; c = ctl.get(); } //isRunning(c) 线程池是否运行状态 //流程2:尝试向阻塞队列中添加任务 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); } //流程3:添加一个Worker。参数1:要执行的任务。 参数2:true:添加核心线程。false:添加临时线程 else if (!addWorker(command, false)) //流程4:线程池已饱和,无法在创建新的Worker,执行饱和策略 reject(command); }
复制代码
复制代码
//创建Worker
    private boolean addWorker(Runnable firstTask, boolean core) {
        retry:
        for (;;) {
            int c = ctl.get();
            ...

            for (;;) {
                //当前工作线程的数量
                int wc = workerCountOf(c);
                if (wc >= CAPACITY ||
                        //core=true,判断当前工作数量是否大于等于corePoolSize,如果是,返回false.
                        //core=false,判断当前工作数量是否大于等于maximumPoolSize,如果是,返回false.
                        wc >= (core ? corePoolSize : maximumPoolSize))
                    return false;
                if (compareAndIncrementWorkerCount(c))
                    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;
        ThreadPoolExecutor.Worker w = null;
        try {
            //通过构造器得到Worker对象
            w = new ThreadPoolExecutor.Worker(firstTask);
            final Thread t = w.thread;
            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());

                    if (rs < SHUTDOWN ||
                            (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            throw new IllegalThreadStateException();
                        //将一个工作线程添加到线程池中。
                        workers.add(w);
                        int s = workers.size();
                        if (s > largestPoolSize)
                            largestPoolSize = s;
                        workerAdded = true;
                    }
                } finally {
                    mainLock.unlock();
                }
                //如果添加Worker成功
                if (workerAdded) {
                    //启动Worker中的线程,并执行Worker的run()方法
                    t.start();
                    workerStarted = true;
                }
            }
        } finally {
            if (! workerStarted)
                addWorkerFailed(w);
        }
        return workerStarted;
    }
复制代码
复制代码
//代表一个工作的线程
//Worker实现了Runnable接口,代表是一个可执行任务
private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
{
    //具体工作的线程
    final Thread thread;
    //第一次要执行的任务
    Runnable firstTask;

    //构造器
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        //创建worker时,传入第一次要执行的任务
        this.firstTask = firstTask;
        //创建线程时传入this(当前Worker对象)。那么当线程启动时,就会执行Worker的run()方法。
        this.thread = getThreadFactory().newThread(this);
    }

    //Worker的执行方法
    public void run() {
        runWorker(this);
    }

    final void runWorker(ThreadPoolExecutor.Worker w) {
        Thread wt = Thread.currentThread();
        //第一次要执行的任务
        Runnable task = w.firstTask;
        w.firstTask = null;
        w.unlock(); // allow interrupts
        boolean completedAbruptly = true;
        try {
            //getTask()从阻塞队列中获取任务
            //任务执行完毕后,会获取新的任务
            while (task != null || (task = getTask()) != null) {
                w.lock();
                ...
                try {
                    beforeExecute(wt, task);
                    Throwable thrown = null;
                    try {
                        //执行任务
                        task.run();
                    }
                    ...
                } finally {
                    //清空已经执行完的任务
                    task = null;
                    w.completedTasks++;
                    w.unlock();
                }
            }
            completedAbruptly = false;
        } finally {
            processWorkerExit(w, completedAbruptly);
        }
    }

    //Worker线程启动后,会不断的使用getTask()方法获取任务执行
    private Runnable getTask() {
        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {
            //省略代码

            try {

                //线程池中的工作线程的数量
                int wc = workerCountOf(c);

                //如果核心线程允许使用keepAliveTime作为过期时间,或者工作线程数大于核心线程数
                boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

                //在workerQueue队列中获取任务
                Runnable r = timed ?
                        //临时线程获取阻塞队列中的头部节点,超过keepAliveTime没有获取到任务就销毁
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        //核心线程阻塞式的获取阻塞队列中的头部节点
                        workQueue.take();
                if (r != null)
                    return r;
                timedOut = true;
            } catch (InterruptedException retry) {
                timedOut = false;
            }
        }
    }
}
复制代码
posted @   翊梦  阅读(377)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
点击右上角即可分享
微信分享提示