ThreadPoolExecutor的核心参数以及线程池状态到底是咋玩的?

如果下面的问题你都会的话就别在这浪费时间啦

PS:像线程池流程源码、核心非核心线程怎么保证一直存活等等问题后面会讲解,此篇幅核心是线程池状态的那个位运算到底是咋玩的。

1、线程池参数

1.1、线程池参数有哪些?都啥意思?


public ThreadPoolExecutor(int corePoolSize,

                          int maximumPoolSize,

                          long keepAliveTime,

                          TimeUnit unit,

                          BlockingQueue workQueue,

                          ThreadFactory threadFactory,

                          RejectedExecutionHandler handler) {

}

线程池在完成初始化之后,默认情况下,线程池中不会有任何线程,线程池会等有任务来的时候再去创建线程,核心线程创建出来后即使超出了线程保持的存活时间配置也不会销毁,核心线程只要创建了就永驻了,就等着新任务来进行处理。除非设置了allowCoreThreadTimeOut,否则核心线程数是保持活动的最小数量0

核心线程忙不过来且任务队列都满了的情况下,还有新任务继续提交进来的话就会新开辟线程,但是也不会任意的开辟线程数量,线程数(包含核心线程数)阈值是maximumPoolSize,达到阈值后还在提交任务的话就走拒绝策略。

如果线程池当前的线程数多于corePoolSize,那么如果多余的线程空闲之间超出keepAliveTime的话,则这些线程就会被回收。

比如:TimeUnit.MILLISECONDSTimeUnit.SECONDS

核心线程数满了后还在继续提交任务到线程池的话,就先进入workQueueworkQueue通常有以下几种选择:

LinkedBlockingQueue:无界队列,默认长度限制是int的最大值。也可以自定义大小。

ArrayBlockingQueue:有界队列,可以自定义大小。

SynchronousQueueExecutors.newCachedThreadPool();默认使用的队列。也不算是个队列,他不没有存储元素的能力。

一般我都采取LinkedBlockingQueue,因为他也可以设置大小,可以取代ArrayBlockingQueue有界队列。

默认采用的是DefaultThreadFactory,主要负责创建线程。newThread()方法。创建出来的线程都在同一个线程组里且优先级是一样的。

默认采取的是AbortPolicy拒绝策略。也就是直接抛出RejectedExecutionException异常。

1.2、线程池是怎么创建线程的?

是通过java.util.concurrent.ThreadFactory#newThread来负责创建的,ThreadFactory是个接口,比如有个Default实现io.netty.util.concurrent.DefaultThreadFactory#newThread(java.lang.Runnable)


public Thread newThread(Runnable r) {

    Thread t = this.newThread(FastThreadLocalRunnable.wrap(r), this.prefix + this.nextId.incrementAndGet());




    try {

        if (t.isDaemon() != this.daemon) {

            t.setDaemon(this.daemon);

        }




        if (t.getPriority() != this.priority) {

            t.setPriority(this.priority);

        }

    } catch (Exception var4) {

        ;

    }




    return t;

}

可以看到如下信息:

真正启动线程是在来任务的时候启动的,之后文章会深度剖析。

1.3、任务拒绝策略有哪些?

顶层父接口是java.util.concurrent.RejectedExecutionHandler,其四大子类如下:

public static class AbortPolicy implements RejectedExecutionHandler {
    public AbortPolicy() {}
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}
DiscardPolicy:直接丢弃任务,源码很简单,我不做任何处理,其实就是丢弃嘛

public static class DiscardPolicy implements RejectedExecutionHandler {

    public DiscardPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

    }

}

public static class DiscardOldestPolicy implements RejectedExecutionHandler {

    public DiscardOldestPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

        if (!e.isShutdown()) {

            e.getQueue().poll();

            e.execute(r);

        }

    }

}

public static class CallerRunsPolicy implements RejectedExecutionHandler {

    public CallerRunsPolicy() { }

    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {

        if (!e.isShutdown()) {

            r.run();

        }

    }

}

1.4、线程池的原理是什么?

ThreadPoolExecutor的核心参数以及线程池状态到底是咋玩的?

线程池工作原理.png

PS:源码会在之后的文章深度剖析。

2、线程池的状态

2.1、线程池有哪几种状态?

如下五种:


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;

2.2、线程池的状态是怎么存储的?

采取一个int类型名叫ctl的变量来存储。低29位存储线程池中线程的活跃数量,高3位存储线程池的状态。

如下源码:


private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

// 29

private static final int COUNT_BITS = Integer.SIZE - 3;

// 线程池容量

private static final int CAPACITY   = (1 << COUNT_BITS) - 1;




// 线程池状态,只有RUNNING状态是负数。

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;

为啥这么设计?因为作者是Doug Lea,当然是节省空间,如果弄一个int存储线程数,一个int存储仅需要3位的状态,那浪费太多空间了。即使用byte存储状态,也浪费了五个bit空间。想想读写锁的设计,也是出自Doug Lea之手。比如:

ThreadPoolExecutor的核心参数以及线程池状态到底是咋玩的?

线程池状态存储.png

2.3、线程池的状态是怎么计算的?


private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));




// 线程池状态,只有RUNNING状态是负数。

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;

我们知道在java中 int 类型占用4个字节32位存储, 上述的几种状态:底层存储二进制为:


1111 1111 1111 1111 1111 1111 1111 1111(-1)

0000 0000 0000 0000 0000 0000 0000 0000(0)

0000 0000 0000 0000 0000 0000 0000 0001(1)

0000 0000 0000 0000 0000 0000 0000 0010(2)

0000 0000 0000 0000 0000 0000 0000 0011(3)

左移< COUNT_BITS = Integer.SIZE - 3 也就是 COUNT_BITS = 29,说明用32位的前3位存储线程池的状态 后29位存储线程池中当前线程的个数, << COUNT_BITS后,变为下面的二进制:

1110 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 int runStateOf(int c) { return c & ~CAPACITY; }

其中CAPACITY = (1 << COUNT_BITS) - 1 转化为二进制为:0001 1111 1111 1111 1111 1111 1111 1111取反"~"后(也就是将前3位全部变为1,后面全部变为0),二进制为:1110 0000 0000 0000 0000 0000 0000 0000接下来,传入的ctl变量和~CAPACITY&操作,只会保留ctl变量的前3位变量,后29位变量全部为0

例如:一个标识当前状态为STOP状态的线程池和当前活跃线程数为2的ctl变量为:0010 0000 0000 0000 0000 0000 0000 0011和上述得到的1110 0000 0000 0000 0000 0000 0000 0000(~CAPACITY)做&操作后得到:


0010 0000 0000 0000 0000 0000 0000 0010

&

1110 0000 0000 0000 0000 0000 0000 0000

=

0010 0000 0000 0000 0000 0000 0000 0000

0010 0000 0000 0000 0000 0000 0000 0000 和上述分析的STOP的状态的二进制相同!即获得了当前线程的状态!

如何获取当前线程池活跃线程数的?


private static int workerCountOf(int c) { return c & CAPACITY; }

上述知道CAPACITY为:0001 1111 1111 1111 1111 1111 1111 1111

例如:一个标识当前状态为STOP状态的线程池和当前活跃线程数为2的ctl变量为:


0010 0000 0000 0000 0000 0000 0000 0010

&    

0001 1111 1111 1111 1111 1111 1111 1111

=

0000 0000 0000 0000 0000 0000 0000 0010

所以结果是二进制10,也就是代表当前线程池中活跃线程数量为2个!

2.4、为什么要用-1代表RUNNING状态?

既然采取3bit作为线程池状态,那么也就是有2的3次幂=8种方式,为什么要采取-1作为RUNNING呢?

因为作者是Doug Lea,Doug Lea又怎么了?Doug Lea是并发大神,神中神。

这样设计的话只需要判断state是不是小于0,如果小于0就代表是RUNNING状态,简单粗暴。所以看到if (state < 0)这种类似的代码就是说如果是运行状态的话,就进入判断。所以线程池五种状态用-1这个唯一的一个负数来表示RUNNING状态。比如如下源码:


private static boolean isRunning(int c) {

    return c < SHUTDOWN;

}

SHUTDOWN是0,所以判断是不是在运行中只需要判断state是不是小于0即可!

2.5、new一个线程池,他的活跃线程数是多少?怎么计算的?

线程池的状态和活跃线程数量都是通过ctl来管理的,高 3位代表状态,低29位代表活跃线程数量。所以看看ctl默认是多少就好啦。


private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

哦嚯,发现是通过ctlOf来计算的,继续看ctlOf的源码:


// rs标识线程池当前状态,wc为work count标识当前工作线程的数量。

private static int ctlOf(int rs, int wc) { return rs | wc; }

上述传入的是ctlOf(RUNNING, 0) ,当前状态为RUNING也就是1110 0000 0000 0000 0000 0000 0000 0000 ,wc为0,也就是当前工作线程数为0,其二进制为0000 0000 0000 0000 0000 0000 0000 0000 ,做"|"或操作,即


1110 0000 0000 0000 0000 0000 0000 0000 

| 

0000 0000 0000 0000 0000 0000 0000 0000

=

1110 0000 0000 0000 0000 0000 0000 0000

上述得到的结果1110 0000 0000 0000 0000 0000 0000 0000就标识,当前线程池状态为RUNNING(-1 << COUNT_BITS),线程池活跃线程个数为0!

2.6、线程池状态是怎么转换的?

展开阅读全文

页面更新:2024-02-09

标签:线程   状态   核心   阈值   队列   源码   也就是说   策略   参数   时间   方法

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top