java基础--ThreadPoolExecutor源码分析

注:环境为mac os x 10,jdk8

0 前言
其实将ThreadPoolExecutor源码分析放在java基础内,个人还是有点犹豫的,权当java基础知识的巩固吧。

废话少说,放码过来。

1、准备知识
磨刀不误砍柴工,先看点准备知识,后面不懂再来看也ok。

1.1 位运算
或、与、非、异或(相同0,不同1,等同于不进位的加法);
带符号左右移:>>、<<,符号位不参与移位运算。
无符号左右移:>>>、<<<,符号位参与移位运算。
移位运算,不足时补0.

1.2 java底层数据存储
这里只讲整形数据,浮点类型在ThreadPoolExecutor内目前不涉及。
以32位jdk为例,第一位为符号位,作用为-1的幂次方,为0表示为整数,为1表示为负数,后面31位为2进制数,这样能表示的最大整数是2^31,最小负数是-2^31-1。其中java底层存储方式是:正数为原码存储,负数为补码存储(符号位不参与取反)。原因:为了让符号位能参与运算(有兴趣可以多了解,忘的差不多了)。

1.3 原子类
原子类的出现,就是为了解决java中的并发问题。其底层是采用硬件进行控制(cas+自旋),保证不会出现并发修改问题。常见的如AtomicBoolean,AtomicInteger,AtomicLong。
原子类基本上都提供了进行赋值、取值的方法。

1.4 标签式用法
如果你没听过也正常,但是如果说goto,那你就知道了。goto就是类似一个标签。
目前已经不提倡(或者说禁止)使用标签了,但是在jdk源码里面还可以找到,可以看出这帮爷们对自己代码的自信。

1.5 钩子函数
钩子函数是指在特定条件下会调用的方法,类似于try catch中的finally,以及object内的finalize方法。

1.6 volatile关键字
最早认识这个关键字是当年进行嵌入式c开发时认识的。
在java中作用也是类似的:一旦变量被volatile关键字修饰,那么jvm在对这个关键字不再进行缓存(硬件缓存,不同于软件缓存),每次读写均直接从系统底层获取,而不再由当前线程保存的值来确定。
这个关键字也可理解为专为并发而生。

1.7 ReentrantLock
一般的锁Lock,都是在对象加锁后就进行互斥的读写了。但是有一种情况,若持有锁的对象就是当前对象,那么此时采用Lock就不适用了。
重入锁,就是在进行对象加锁时,判断对该对象加锁的线程是否为当前线程,是的话就可以进行操作,不是的话根据公平锁与非公平锁有各自的处理逻辑。

1.8 自旋
自旋其实就是空循环,直到外部条件变化,才会跳出循环。
类似于这样:

for(;;;){
    ……
    if(………){
        break;
    }
}

或者

while(true){
    ……
    if(………){
        break;
    }
}

为什么会采用这种方式呢,因为这种操作一般执行时间很短,若跳出再执行的话,对系统资源消耗比较大。而采用自旋锁的话,当前线程一直占用cpu的执行时间,避免进行上下文的切换,反而提高了效率。
但是不建议这样写,首先第一个是不容易看懂,一旦关联代码有变化,这个地方就可能成为一个坑点;其次,现在的很多代码检测工具,也会认为这种代码是有问题的,可能都无法提交。

2、类图分析

类图

ThreadPoolExecutor的类结构比较简单,继承自抽象类AbstractExecutorService,在最上层的Executor接口中,只有一个方法:

void execute(Runnable command);

所有ThreadPoolExecutor中要执行的线程,须实现了Runnable接口,才可加入线程队列。Runnable接口也简单,就一个方法:

public abstract void run();

也就是说,所有线程,最终都会执行这个run方法。

3、常量分析
ThreadPoolExecutor的常量分析,须和几个方法一起分析,才能看出设计的妙处。

3.1 关键常量与成员变量分析
先看定义几个比较关键的常量,理解了这里的东西,这个类就差不多读懂一半了。

3.1.1 常量
线程池的状态及说明,一定要同线程的状态区分开。

private static final int COUNT_BITS = Integer.SIZE - 3;
//0001 1111 1111 1111 1111 1111 1111 1111
private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
//1110 0000 0000 0000 0000 0000 0000 0000
//能接受新任务,队列中的任务可继续运行
private static final int RUNNING    = -1 << COUNT_BITS;
//0000 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,队列中的任务仍可继续执行
private static final int SHUTDOWN   =  0 << COUNT_BITS;
//0010 0000 0000 0000 0000 0000 0000 0000
//不再接受新任务,不再执行队列中的任务,中断所有执行中的任务(发中断消息)
private static final int STOP       =  1 << COUNT_BITS;
//0100 0000 0000 0000 0000 0000 0000 0000
//所有任务均已终止,workerCount的值为0,转到TIDYING状态的线程即将要执行terminated()钩子方法.
private static final int TIDYING    =  2 << COUNT_BITS;
//0110 0000 0000 0000 0000 0000 0000 0000
//terminated()方法执行结束
private static final int TERMINATED =  3 << COUNT_BITS;

我们先看5个状态,只看最高的3位,分别是:

RUNNING    = 111
SHUTDOWN   = 000
STOP       = 001
TIDYING    = 010
TERMINATED = 011

目前看到这,我们要记住的一点就是:高3位用来进行表示线程池的运行状态。至于剩余的29位,我们下面还有分析。但是对于这几种状态的关系,我们来看看源码中的注释。

3.1.2 线程池状态转换
原文是英文,我也是看的迷迷糊糊的,就不献丑了,结合网上找的帖子,解释如下:

各状态之间可能的转变有以下几种:
RUNNING -> SHUTDOWN
    调用了shutdown方法,线程池实现了finalize方法。
    在finalize内调用了shutdown方法。
    因此shutdown可能是在finalize中被隐式调用的
(RUNNING or SHUTDOWN) -> STOP
    调用了shutdownNow方法
SHUTDOWN -> TIDYING
    当队列和线程池均为空的时候
STOP -> TIDYING
    当线程池为空的时候
TIDYING -> TERMINATED
    terminated()钩子方法调用完毕

3.1.3 关键成员变量与私有方法
看源码:

//初始化线程数
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
//获取当前线程的运行状态
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; }

看到这,我们分析一下获取当前线程的运行状态与线程数方法:

//获取当前线程的运行状态
//~CAPACITY = 1110 0000 0000 0000 0000 0000 0000 0000
private static int runStateOf(int c)     { return c & ~CAPACITY; }

取任意一个运行状态可知,与容量的取反进行与操作后,低29位全部屏蔽了,因此此时返回的必然是运行状态。
同理:

//获取当前的线程数
//CAPACITY = 0001 1111 1111 1111 1111 1111 1111 1111
private static int workerCountOf(int c)  { return c & CAPACITY; }

这个时候进行与操作,屏蔽了高3位,只有低29位参与运算,可以快速的得出当前线程池中的线程数。

3.1.4 点评
将运行状态设置为小于0的数,便于判断当前线程池是否处于running状态;
仅使用32位存储线程池状态与线程池内的线程数,若需要获取线程池信息,只需要一个int即可,可见jdk底层源码的精简;
采用位运算,提高了执行效率;
同时,线程池状态还有扩增空间(2^3=8,目前只有5种状态),而线程池最大容量2^29,也可保证在绝大部分应用中是不会溢出的。而在源码中也声明了:如果在未来这个也成为一个问题,那么可以扩增为AtomicLong。

3.2 其他常量与成员变量
分析几个重要的

//待执行线程队列
private final BlockingQueue<Runnable> workQueue;
//锁,基于重入锁,线程池核心之一
private final ReentrantLock mainLock = new ReentrantLock();
//线程队列,这是该线程池内已有线程
//注意与workQueue的区别
private final HashSet<Worker> workers = new HashSet<Worker>();
//多线程协调通信
private final Condition termination = mainLock.newCondition();
//拒绝handler,用于线程池不接受新加线程时的处理方式
//分为系统拒绝(线程池要关闭等),与线程池饱和(已达线程池最大容量)
private volatile RejectedExecutionHandler handler;
//线程工厂,新建线程池时带入
private volatile ThreadFactory threadFactory;
//默认拒绝向线程池中新加线程的方式:丢弃
private static final RejectedExecutionHandler defaultHandler =new AbortPolicy();

4、常用方法分析
看完了常量,我们来开始进入正题吧。

4.1 execute
前面的类图也可以看到,向线程池中新增task时,会调用该方法。我们先看源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    //获取当前的线程池信息,状态+线程数量
    int c = ctl.get();
    //若当前线程数量未达corePoolSize
    //corePoolSize可以理解为最小容量
    if (workerCountOf(c) < corePoolSize) {
        //向线程池中将本thread加进去
        //若加成功,则直接返回
        //未加成功的原因,下面还有源码分析
        if (addWorker(command, true))
            return;
        //这里就是未加成功,再次获取当前线程池信息
        c = ctl.get();
    }
    //再次检测当前线程池的运行状态
    //并将当前线程加入到等待队列内
    if (isRunning(c) && workQueue.offer(command)) {
        //又获取一次线程池信息,跪下了
        int recheck = ctl.get();
        //检查当前线程池状态是否不在Running状态了
        //若是,将线程cmd从等待队列内移除
        //这个时候存在一种case,线程池不处于running状态
        //但是remove失败了,这个时候看具体的queue处理了
        //线程池还是很忠实的去尝试interrupt
        if (! isRunning(recheck) && remove(command))
            //线程池非running状态,并且移除cmd成功
            //那么按处理策略拒绝cmd加入到线程池内
            reject(command);
        //检查一下当前线程池的缓存队列数量是否为0
        else if (workerCountOf(recheck) == 0)
            //根据情况是否要将新起一个线程
            addWorker(null, false);
    }
    //若新起一个线程失败
    else if (!addWorker(command, false))
        //拒绝当前cmd
        reject(command);
}

从源码可以看出,为避免数据过时,每次在进行操作前,都重新获取一次当前线程的信息。由于这是对线程池的操作,一般场景下,也不会出现很大量的并发向线程池新增线程的操作(如果有,首先得分析你的业务场景是否有必要这么做,如果必要且现有代码不满足你的需求,那么你可以覆写其中的部分代码)。

4.2 addWorker
看了上面的execute方法,我们再看addwork方法。这个方法是向线程池尝试新增一个线程,并执行firstTask。

//尝试向线程池内新增一个线程
private boolean addWorker(Runnable firstTask, boolean core) {
    //注意标签
    retry:
    for (;;) {
        int c = ctl.get();
        int rs = runStateOf(c);

        // Check if queue empty only if necessary.
        //若线程池处于非运行状态
        //且
        //或rs不处于SHUTDOWN状态(STOP、TIDYING、TERMINATED 之一)
        //或firstTask不为空
        //或缓冲队列为空
        //那么返回false,表明新增一个线程失败(执行firstTask 也失败)
        if (rs >= SHUTDOWN &&
            ! (rs == SHUTDOWN &&
               firstTask == null &&
               ! workQueue.isEmpty()))
            return false;

        //此时线程池处于running状态,firstTask不为空
        //且缓冲队列不为空,此时需要新增一个线程
        for (;;) {
            //获取线程池当前线程数量
            int wc = workerCountOf(c);
            //若线程池超过最大容量,或大于设定的容量
            //corePoolSize与maximumPoolSize均为传入的参数
            //那么直接返回false
            if (wc >= CAPACITY ||
                wc >= (core ? corePoolSize : maximumPoolSize))
                return false;
            //线程池未过限,那么采用cas机制,将线程池计数器扩增1,跳出标签
            if (compareAndIncrementWorkerCount(c))
                break retry;
            //获取当前线程池信息
            c = ctl.get();  // Re-read ctl
            //若线程池状态有变更,从标签处重新循环
            if (runStateOf(c) != rs)
                continue retry;
            //若线程池状态未变化,继续内层的for循环
        }
    }

    //上面若将线程池计数器加1了
    //这里就要对线程池扩增了
    boolean workerStarted = false;
    boolean workerAdded = false;
    Worker w = null;
    try {
        //创建一个线程实例
        w = new Worker(firstTask);
        //获取线程
        //不在创建实例时直接run该线程,是避免构造函数未执行完,就run导致的异常
        final Thread t = w.thread;
        if (t != null) {
            //重入锁
            final ReentrantLock mainLock = this.mainLock;
            //锁上,走起
            mainLock.lock();
            try {
                //获取线程池状态
                int rs = runStateOf(ctl.get());
                //若线程池状态为running状态
                //或为SHUTDOWN且传入的线程为空
                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();
            }
            //判断线程是否加入成功
            if (workerAdded) {
                //加入成功
                //启动当前线程,将当前线程交有os管理
                t.start();
                //设置标志位
                workerStarted = true;
            }
        }
    } finally {
        //若未启动成功
        if (! workerStarted)
            //回滚当前新起线程操作
            //移除当前新增失败的线程
            //将线程池计数器减1
            //尝试中断线程池或者中断当前线程
            addWorkerFailed(w);
    }
    //返回标志位,是否新增线程成功
    return workerStarted;
}

感觉这段代码还没有讲清楚,后续有空可以再看看。

4.3 tryTerminate

还记得execute方法内的remove方法不,看源码:

public boolean remove(Runnable task) {
    boolean removed = workQueue.remove(task);
    tryTerminate(); // In case SHUTDOWN and now empty
    return removed;
}

看其中的核心代码段tryTerminate:

//方法作用:进行状态转换以及中断一个线程
//当池的状态为SHUTDOWN且任务队列为空,需要将池的状态转变为TERMINATED;
//当池的状态为STOP且池中的当前活动线程数为0,要将池的状态转换成TERMINATED
//以及当线程池内的线程不为空时,中断第一个线程
final void tryTerminate() {
    for (;;) {
        int c = ctl.get();
        //若
        //或线程池处于运行状态
        //或线程池已处于TIDYING/TERMINATED
        //或:线程池处于SHUTDOWN且缓冲队列不为空
        //返回,此时线程池状态不用做任何变更
        if (isRunning(c) ||
            runStateAtLeast(c, TIDYING) ||
            (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))
            return;
        //排除线程池状态不需要变更的状态,下面开始改变线程池状态
        //若线程池内线程数量不为0
        if (workerCountOf(c) != 0) { // Eligible to terminate
            //给线程池的首个线程发中断消息
            //进行尝试中断其运行
            interruptIdleWorkers(ONLY_ONE);
            return;
        }

        //获得全局锁
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            //这个地方的ctlOf作用是什么,没明白
            //设置线程池的状态为TIDYING
            if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
                try {
                    //这个方法为留给ThreadPoolExecuor子类使用,为空代码
                    terminated();
                } finally {
                    //将线程状态设置为TERMINATED
                    ctl.set(ctlOf(TERMINATED, 0));
                    //给其他线程发消息,告知该线程池即将关闭
                    //与termination对应的还有一个源码分析:awaitTermination
                    termination.signalAll();
                }
                return;
            }
        } finally {
            mainLock.unlock();
        }
    }
}

4.4 run && runWorker
在execute我们介绍了,线程池内新增线程后,都是要调用run方法来进行执行的。我们看run源码:

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

再看runwork方法:

final void runWorker(Worker w) {
    Thread wt = Thread.currentThread();
    Runnable task = w.firstTask;
    w.firstTask = null;
    w.unlock(); // allow interrupts
    boolean completedAbruptly = true;
    try {
        //循环
        //从缓冲队列内拿出task,直到task内为空
        while (task != null || (task = getTask()) != null) {
            w.lock();
            // If pool is stopping, ensure thread is interrupted;
            // if not, ensure thread is not interrupted.  This
            // requires a recheck in second case to deal with
            // shutdownNow race while clearing interrupt
            //这个if条件写的真心难读,不能格式化一下吗
            //实际是这样的:
            //!wt.isInterrupted()
            //    && (runStateAtLeast(ctl.get(), STOP)
            //            || (Thread.interrupted()
            //                && runStateAtLeast(ctl.get(), STOP)))
            //当前线程状态为非中断
            //且
            //或 线程池状态为STOP、TIDYING、TERMINATED
            //或 当前线程状态为interrupted,且线程池状态为STOP、TIDYING、TERMINATED
            if ((runStateAtLeast(ctl.get(), STOP) ||
                 (Thread.interrupted() &&
                  runStateAtLeast(ctl.get(), STOP))) &&
                !wt.isInterrupted())
                //终端当前线程
                //jdk源码注释写的很清楚
                //这个地方发终端消息是与shutdownnow进行竞争
                //因为这个地方中断以后,状态就变为stop及以上状态了
                //发中断后就会不能再往线程池中新增任务
                //但是可以继续执行线程池内的任务
                wt.interrupt();
            try {
                //钩子函数,继承本类的子类可以进行一些操作
                //在本类内为空方法
                beforeExecute(wt, task);
                Throwable thrown = null;
                try {
                    //直接在当前线程内运行task
                    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 {
                    //同beforeExecute,也是钩子函数
                    afterExecute(task, thrown);
                }
            } finally {
                //每次执行完,将completedTasks自增
                //解锁
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        //标志位,能走到这,就说明线程执行没有异常中止
        completedAbruptly = false;
    } finally {
        //当前线程执行完毕,进行收尾工作
        processWorkerExit(w, completedAbruptly);
    }
}

4.5 processWorkerExit
看完执行线程内的任务,我们先看线程退出清理方法processWorkerExit:

private void processWorkerExit(Worker w, boolean completedAbruptly) {
    //为true则表明是该线程意外中断的
    //将线程池的线程数减1
    if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
        decrementWorkerCount();

    final ReentrantLock mainLock = this.mainLock;
    mainLock.lock();
    try {
        //扩大统计
        completedTaskCount += w.completedTasks;
        //线程池内移除该线程
        workers.remove(w);
    } finally {
        mainLock.unlock();
    }

    //改变线程池的状态
    //或者中断第一个线程
    tryTerminate();

    int c = ctl.get();
    //若线程池的状态处于STOP、TIDYING、TERMINATED
    if (runStateLessThan(c, STOP)) {
        //线程非意外退出
        if (!completedAbruptly) {
            //这个地方其实就是判断当前线程池的的线程是否还有
            //若线程池大于系统最小值,那么就返回
            int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
            if (min == 0 && ! workQueue.isEmpty())
                min = 1;
            if (workerCountOf(c) >= min)
                return; // replacement not needed
        }
        //这个时候有2种情况
        //1、线程意外退出,尝试新增一个线程
        //2、线程非意外退出,但是线程池还有线程,根据线程池内的情况决定是否要新增线程
        addWorker(null, false);
    }
}

4.6 getTask
在runworker内还有一个getTask方法,这个方法是从线程池内获取一个task,成功返回一个Runnable的task,失败返回null。

//从线程内轮询拿一个task出来
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.
        //线程池状态为非运行状态
        //且
        //或线程池处于STOP、TIDYING、TERMINATED
        //或线程池已空
        //这个时候将线程池的的计数减1
        //由于线程池已空了,返回空
        //这个rs >= SHUTDOWN不懂,看起来作用不大??????????
        if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
            decrementWorkerCount();
            return null;
        }

        int wc = workerCountOf(c);

        // Are workers subject to culling?
        //标志位
        //或 线程池允许核心线程超时
        //或 当前线程数大于最小线程数
        boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

        //当前线程池中线程数大于最大线程数 或 timed 且 timedOut均为true
        //且
        //当前线程数大于1,或 线程池中可用线程为空
        if ((wc > maximumPoolSize || (timed && timedOut))
            && (wc > 1 || workQueue.isEmpty())) {
            //若线程池中线程数成功减1
            //返回空
            //否则继续下一轮自旋
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            //根据标志位从缓冲队列内轮询或者直接拿一个task
            //拿到了则成功返回
            ///没拿到或捕获到中断异常,则置超时标志位为true
            Runnable r = timed ?
                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                workQueue.take();
            if (r != null)
                return r;
            timedOut = true;
        } catch (InterruptedException retry) {
            timedOut = false;
        }
    }
}

4.7 小结

总的来说,这个线程池的源码我是半知半解的,对于线程池内的竞态条件还是没有完全理解。后续有新的理解,可以继续更新。

5、jdk实例源码分析
看完源码,没有一点实例,总是感觉很枯燥。还好,jdk源码自带了几个使用这个类的绝佳例子。

5.1 Executors
Executors是日常进行多线程开发使用较多的一个类,用的比较多的是其中2种:newFixedThreadPool、newCachedThreadPool.

5.1.1 newFixedThreadPool
这个比较好理解,固定大小的线程池。经过重载,有几种形式,我们来看其中一个源码:

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads,
        nThreads,
        0L,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
    }

这个其实也很好理解,新起一个线程池:
线程池最小大小和最大大小保持一致,若该线程没有可供执行的任务,那么即刻关闭该线程,
当有新task加入时没有空闲线程,那么将该线程加入到无界阻塞队列中,等待执行;

5.1.2 newCachedThreadPool
这个方法是新起一个线程池,允许线程池内的线程在空闲一定时间后,若还没有新任务加入,才关闭该线程,而不是直接关闭该线程。

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
        60L, TimeUnit.SECONDS,
        new SynchronousQueue<Runnable>());
    }

这个的意思是:新起一个线程池,特点:
最小线程数为0,最大为近乎无限,在线程池内该线程持续60s没有任务执行时,才关闭该线程,当有任务时,直接新起一个线程进行执行,若没有线程,则执行默认的处理策略进行处理(默认直接丢弃)。

5.1.3 简单对比
newFixedThreadPool由于有一个近乎无限的缓冲队列,那么任意多的请求过来,都可以放到缓冲队列中等待处理,但是由于处理的线程数固定,对于突发性的业务爆发,无法进行应对(若平时开启的很大线程数,浪费系统资源),可能会导致系统反应缓慢;

newCachedThreadPool由于在理论上只要有请求,都可以即时开启一个新线程进行响应,因而相应相对较快;但是一个系统内的最大线程数是有限的,一旦超过系统最大的线程数,那么多余的请求都会被直接抛弃掉(这种情况很难排查),这个时候可能反而起反作用。若同时开启的线程数较多,且jvm参数设置不合理(如内存分配较小,gc参数设置不合理),那么可能会导致系统发生频繁的gc,或者直接导致oom事件发生。

5.2 ScheduledThreadPoolExecutor
这个只是简介。这个类是继承的ThreadPoolExecutor,并且实现了ScheduledExecutorService接口,简单来说,这个类可以进行一些周期性的线程调度工作。
那么这个时候肯定可以联想到日常业务中的定时器。的确,不过用这个做定时器,现在很少了(有了spring-quartz)。

6、小结
由于之前对这个关注不多,只是想了解一下,这篇文章前后花了一周的业余时间才完成。但是个人感觉只是略知一二,还没有完全掌握其中的核心,若你有好的理解,可以回复我。

jdk源码分析,到这差不多告一段落了。后续还有其他的一些分享分析,欢迎继续留意。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值