Java线程池 你知它有多深

总图

Thread

线程是一种资源,并不是只存在程序的世界里。程序,只是对生活的一种抽象表述。

比如车站的售票窗口、退票窗口、检票窗口,每个窗口都在做不同的事情,就是同时在运行的不同线程。

线程多了,需要管理,不同的线程也要能保证不会互相干扰,各做各的。

线程像人的能量,线程池好比人能量的总场,这个比喻可能不太恰当,因为人同时做好几件事情时,哪怕看似“同时”的,也是分心做的,是自己的CPU在不断切换时间片,那只是“并发”,而不是“并行”。

线程的Life

ThreadPoolExecutor

线程池状态

public class ThreadPoolExecutor extends AbstractExecutorService {
    /**
     * The main pool control state, ctl, is an atomic integer packing
     * two conceptual fields
     *   workerCount, indicating the effective number of threads
     *   runState,    indicating whether running, shutting down etc
     *
     * The runState provides the main lifecycle control, taking on values:
     *
     *   RUNNING:  Accept new tasks and process queued tasks
     *   SHUTDOWN: Don't accept new tasks, but process queued tasks
     *   STOP:     Don't accept new tasks, don't process queued tasks,
     *             and interrupt in-progress tasks
     *   TIDYING:  All tasks have terminated, workerCount is zero,
     *             the thread transitioning to state TIDYING
     *             will run the terminated() hook method
     *   TERMINATED: terminated() has completed
复制代码
  • runState:线程池运行状态
  • workerCount:工作线程的数量
    // Android-added: @ReachabilitySensitive
    @ReachabilitySensitive
    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY   = (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;
    
    // Packing and unpacking ctl
    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; }
复制代码

线程池用一个32位的int来同时保存runState和workerCount,其中高3位(第31到29位)是runState,其余29位是workerCount(大约500 million)。

来个图看看存储结构

Construtor

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;
    }
复制代码

它的参数

  • corePoolSize 核心线程数,好比班干部的人数。

  • maximumPoolSize 最大线程数,好比教室里的座位数。 当提交任务数超过了这个最大值,线程还有拒绝策略——RejectExecutionHandler,做不动了嘛。

  • keepAliveTime 除核心线程外的空闲线程保持存活时间。 当线程池里线程数超过corePoolSize数量了,keepAliveTime时间到,就把空闲线程关了,不然也闲置了呀,节省能量嘛。

  • workQueue 通过workQueue,线程池实现了阻塞功能。 当线程池中的线程数超过它的corePoolSize的时候,线程会进入阻塞队列进行阻塞等待。

  • threadFactory 创建线程的工厂。 所有的线程都通过这个Factory创建(通过addWorker方法)。

  • handler 线程池的饱和策略。

4种策略:
  • AbortPolicy:直接抛出异常,默认策略;
  • CallerRunsPolicy:用调用者所在的线程来执行任务;
  • DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务;
  • DiscardPolicy:直接丢弃任务。

更多被调用的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), handler);
    }
复制代码

排队策略

  • SynchronousQueue 直接提交。直接提交任务给线程,而不是保存它们。

  • LinkedBlockingQueue 无界队列。 当核心线程都在忙的时候,用一个无界队列存放提交的任务。 线程数不会超过核心线程数,maximumPoolSize设置也无效。

  • ArrayBlockingQueue 有界队列。 防止资源被消耗完,队列也是有上限的。

Executors

Executors.defaultThreadFactory()是Executors静态工厂里默认的threadFactory。 后面再详细说。

源码分析

Worker —— 工作线程

线程池创建线程时,会将线程封装成工作线程Worker,就是在线程池里干活的人。

private final class Worker
        extends AbstractQueuedSynchronizer
        implements Runnable
复制代码

既实现了Runnable,又继承了AbstractQueuedSynchronizer(AQS),所以其既是一个可执行的任务,又可以达到锁的效果。

Worker和Task的区别: Worker是线程池中的线程,而Task虽然是runnable,但是并没有真正执行,只是被Worker调用了run方法。

/**
  * Creates with given first task and thread from ThreadFactory.
     * @param firstTask the first task (null if none)
     */
    Worker(Runnable firstTask) {
        setState(-1); // inhibit interrupts until runWorker
        this.firstTask = firstTask;
       this.thread = getThreadFactory().newThread(this);
   }
复制代码

先来看看总体流程图~~

注意点:

  • 线程池里有很多Worker,核心的成员数就是corePoolSize,池子里最多能容纳的Worker数是maximumPoolSize;
  • workQueue是等待中的任务队列。
  • 默认 corePoolSize 之内的线程是不会被回收的。

看源码

execute(Runnable command)
public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 如果当前正在运行的线程数 < corePoolSize,尝试用给到的command来启动一个新线程作为第一个任务。
         * 调用addWorker方法,检查runState和workerCount,
         * 并且如果增加线程的话,能防止产生错误警报,如果不能增加线程,则返回false。

         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 如果一个任务被成功地加到队列里,仍然需要双重检验来确认是否需要新建一个线程
         *(因为可能在上一次检查后,已经存在的线程已经died)或者进入这个方法后,线程池已经被关闭了。
         * 所以我们需要再次检查state,如果线程池停止了需要回滚入队列,如果池中没有线程了,新建一个线程。

         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.

         * 如果不能把任务加入队列(可能线程池已经关闭或者满了),那么需要新开一个线程(往maxPoolSize发展)。
         * 如果失败的话,说明线程池shutdown了或者满了,就要拒绝这个任务了。
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        /**
         * 2、如果线程池RUNNING状态,且入队列成功
         */
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            
            //如果再次校验过程中,线程池不是RUNNING状态,
            // 并且remove(command)--workQueue.remove()成功,拒绝当前command
            if (! isRunning(recheck) && remove(command))
                reject(command);

            //为什么只检查运行的worker数量是不是0呢?? 为什么不和corePoolSize比较呢??
            // 只保证有一个worker线程可以从queue中获取任务执行就行了??
            // 因为只要还有活动的worker线程,就可以消费workerQueue中的任务
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        /**
         * 3、如果线程池不是running状态 或者 无法入队列
         *    尝试开启新线程,扩容至maxPoolSize,
         *    如果addWork(command, false) 失败了,拒绝当前command
         */
        else if (!addWorker(command, false))
            reject(command);
    }
复制代码

看注解有点费劲,把execute方法里的注释单独列出来,一步步说得很清楚:

  1. 如果当前正在运行的线程数 < corePoolSize,尝试用给到的command来启动一个新线程作为第一个任务。 调用addWorker方法,检查runState和workerCount,并且如果增加线程的话,能防止产生错误警报,如果不能增加线程,则返回false。

  2. 如果一个任务被成功地加到队列里,仍然需要双重检验来确认是否需要新建一个线程。 (因为可能在上一次检查后,已经存在的线程已经died)或者进入这个方法后,线程池已经被关闭了。所以我们需要再次检查state,如果线程池停止了需要回滚入队列,如果池中没有线程了,新建一个线程。

  3. 如果不能把任务加入队列(可能线程池已经关闭或者满了),那么需要新开一个线程(往maxPoolSize发展)。如果失败的话,说明线程池shutdown了或者满了,就要拒绝这个任务了。

(这是参考来的图)

自己画一个:

好像还是很复杂,再简单点:

总的来说,就是:

  1. 先看能不能加入核心线程里,
  2. 再看能不能加入workQueue,
  3. 最后看有没有超过线程池的最大数量,超过的话就拒绝这个task了。

Executors

它是一个Java中的工具类。提供工厂方法来创建不同类型的线程池。 用它可以很方便地创建出下面几种线程池来。

常用线程池特点适应场景
newSingleThreadExecutor单线程的线程池用于需要保证顺序执行的场景,并且只有一个线程在执行
newFixedThreadPool固定大小的线程池用于已知并发压力的情况下,对线程数做限制。
newCachedThreadPool可以无限扩大的线程池比较适合处理执行时间比较小的任务。
newScheduledThreadPool可以延时启动,定时启动的线程池适用于需要多个后台线程执行周期任务的场景。
newWorkStealingPool拥有多个任务队列的线程池可以减少连接数,创建当前可用cpu数量的线程来并行执行。

比如这样创建:

ExecutorService singleService = Executors.newSingleThreadExecutor();

ExecutorService fixedService = Executors.newFixedThreadPool(9);

ExecutorService cacheService = Executors.newCacheThreadPool();
复制代码

或者通过ThreadPoolExecutor的构造函数自定义需要的线程池。

先写到这里~

ref

转载于:https://juejin.im/post/5cde2972f265da7e506593f3

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值