CachedThreadPool

我们对CachedThreadPool可能会有以下疑问:

  1. CachedThreadPool是怎么做到创建无数个工作线程的?

  2. 为什么说CachedThreadPool可能导致导致cpu负载过高?

  3. corePoolSize和maximumPoolSize在CachedThreadPool怎样设置?

  4. keepAliveTime在CachedThreadPool怎样设置?

  5. 拒绝策略在什么条件下会生效,CachedThreadPool使用了哪种拒绝策略

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

总结:

  1. corePoolSize是0,maximumPoolSize是Integer.MAX_VALUE,根据我们之前对源码的分析,CachedThreadPool不会有核心线程,核心线程数的addworker方法不会执行,直接尝试加入queue中。

  2. keepAliveTime是60s,也就是60s从队列中没有拿到任务,worker就会自动销毁,销毁过程在fixed的分析中已经看过了

  3. queue使用的是SynchronousQueue,这个就是CachedThreadPool实现不停创建工作线程的关键,因为线程池处理任务分为三步:有任务过来先创建核心线程,当线程数达到核心线程数时则会进入等待队列,当等待队列满了则再创建非核心线程,直到线程数达到最大线程数后执行拒绝策略,所以可以猜测SynchronousQueue应该是没有容量的队列,放入一个offer操作必须有一个poll操作在等待,没有的话就执行入队失败,然后创建非核心线程进行处理

  4. 线程工厂和拒绝策略都没有指定,则使用的就是默认的,默认的拒绝策略是丢出一个异常

SynchronousQueue

CachedThreadPool使用了无参构造器,所以实际使用的是TransferStack,先进后出的方式。TransferStack无参构造器啥事情都没做。

SynchronousQueue.offer(e)方法

public boolean offer(E e) {
if (e == null) throw new NullPointerException();
return transferer.transfer(e, true, 0) != null;
}

实际上就是调用了TransferStack.transfer(e, true, 0)方法,注意参数:e=e,timed=true,nanos=0.

offer(”张三“)

if (h == null || h.mode == mode) {  // empty or same-mode
    if (timed && nanos <= 0) {      // can't wait
        if (h != null && h.isCancelled())
            casHead(h, h.next);     // pop cancelled node
        else
            return null;
    } else if (casHead(h, s = snode(s, e, h, mode))) {
        SNode m = awaitFulfill(s, timed, nanos);
        if (m == s) {               // wait was cancelled
            clean(s);
            return null;
        }
        if ((h = head) != null && h.next == s)
            casHead(h, s.next);     // help s's fulfiller
        return (E) ((mode == REQUEST) ? m.item : s.item);
    }
} 

此时h==null成立,且   if (timed && nanos <= 0) 这个if也会成立,所以直接就return null,offer方法就返回false入队失败,这样就进入了创建非核心线程的流程中了

那什么时候会入队成功呢?

根据我们之前的分析,woker线程在执行完firstTask之后会执行getTask方法从队列中获取任务,SynchronousQueue的keepAliveTime是60s,所以会执行poll方法

可以看到SynchronousQueue的poll方法也是调用TransferStack.transfer(e, true, 0)方法,注意参数:e=null,timed=true,nanos=keepAliveTime.

此时会进入else if分支,此时h=nuCachedThreadPoolll,mode=REQUEST,e=null,封装成SNode并把s设置成head。设置成功则会通过awaitFulfill方法来等待有其他线程放入任务

awaitFulfill(SNode s, boolean timed, long nanos) 方法

SNode awaitFulfill(SNode s, boolean timed, long nanos) {
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    Thread w = Thread.currentThread();
shouldSpin(s)判断需不需要自旋,如果当前节点是s或head为null或s还没有匹配到任务则需要,需要的话判断timed是不是true,那肯定是true,所以进入自旋
spins=maxTimedSpins=32;
    int spins = (shouldSpin(s) ?
 (timed ? maxTimedSpins : maxUntimedSpins) : 0);
    for (;;) {
if (w.isInterrupted())
    s.tryCancel();
SNode m = s.match;
如果匹配到了任务则return匹配的节点m
if (m != null)
    return m;
判断等待时间是否已到,到了则把match指针指向自己
if (timed) {
    nanos = deadline - System.nanoTime();
    if (nanos <= 0L) {
s.tryCancel();
continue;
    }
}
判断自旋次数,每次自旋都-1,直到shouldSpin条件不成立或到达自旋次数,如果已经自旋了 0)次还没有匹配到任务则parkNanos(this, nanos)
if (spins > 0)
    spins = shouldSpin(s) ? (spins-1) : 0;
else if (s.waiter == null)
    s.waiter = w; // establish waiter so can park next iter
else if (!timed)
    LockSupport.park(this);
else if (nanos > spinForTimeoutThreshold)
    LockSupport.parkNanos(this, nanos);

所以当getTask()时会park当前线程等待keepAliveTime时间

什么时候来唤醒getTask()线程呢?

那肯定是有任务入队的时候,比如现在有线程来offer(”张三“)进入TransferStack.transfer(e, true, 0)方法,注意参数:e=e,timed=true,nanos=0.

把当前节点封装成s,然后设置成head,获取s的next节点,实际就是之前那个park节点,然后使用tryMatch方法进行cas设置park节点match变量。设置成功则unpark之前那个REQUEST节点

 

总结:

  1. 为什么说CachedThreadPool是一个没缓存的线程池呢,因为CachedThreadPool的核心线程数是0,所以有线程来offer元素时都会先尝试放入queue中,但CachedThreadPool使用的queue在没有其他线程在等待poll时是会入队失败的,此时就会起新的线程来执行任务,所以不会有任何任务会放入queue中进行等待,offer元素时不是之前的线程已经执行完任务从queue中poll任务就是直接起新的工作线程执行任务,所以呢CachedThreadPool不会有延迟现象产生,缺点是任务时间执行过长可能导致启动的工作线程过多导致cpu飙升

  2. SynchronousQueue主要通过offer和poll两个方法来保证queue中不保存任务,offer的时候会判断由node组成的单链表有没有poll线程在等待,如果没有线程在等待获取任务则会失败,而poll线程则会先封装成node组成单链表,然后自旋等待offer任务,如果期间没有任务则会park进行睡眠,直到有offer任务出现,当有offer任务出现时会先把自己封装成node节点,然后跟next节点进行匹对,也就是匹对poll线程,匹对成功则弹出这两个节点,完成任务的承接,这期间都是无锁操作,使用了大量cas。

公众号同名,欢迎关注 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值