Java并发之线程池ThreadPoolExecutor源码分析学习,java面试题,java高级笔试题

java线程池源码分析,彻底扫清线程池知识盲区(建议收藏) 1.计算机的基础知识 位逻辑运算符: &: 位与运算符,只有两个操作数都是true,结果才是true。 |: 位或运算符,只有两个操作数都是false,结果才是false。 ~: 位非运算符:如果位为0,结果是1,如果位为1,结果是0. ^: 位异或运算:两个数转为二进制,然后从高位开始比较,如果相同则为0,不相同则为1。 位移运算: 无符号左移 无符号右移 :带符号右移(没有带符号左移这种操作) 二进制: 二进制都是以补码的形式表示的 正数的原码,反码,补码都一样; 要得到负数的补码,必须先求负数 阅读详情


写在最前面,我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。扫码加微信好友进【程序员面试学习交流群】,免费领取。也欢迎各位一起在群里探讨技术。

 

线程池学习

 

 

以下所有内容以及源码分析都是基于JDK1.8的,请知悉。


 

我写博客就真的比较没有顺序了,这可能跟我的学习方式有关,我自己也觉得这样挺不好的,但是没办法说服自己去改变,所以也只能这样想到什么学什么了。

​ 池化技术真的是一门在我看来非常牛逼的技术,因为它做到了在有限资源内实现了资源利用的最大化,这让我想到了一门课程,那就是运筹学,当时在上运筹学的时候就经常做这种类似的问题。

​ 言归正传吧,我接下来会进行一次线程池方面知识点的学习,也会记录下来分享给大家。

线程池的内容当中有涉及到AQS同步器的知识点,如果对AQS同步器知识点感觉有点薄弱,可以去看我的上一篇文章。

线程池的优势

​ 既然说到线程池了,而且大多数的大牛也都会建议我们使用池化技术来管理一些资源,那线程池肯定也是有它的好处的,要不然怎么会那么出名并且让大家使用呢?

​ 我们就来看看它究竟有什么优势?




  •  
  • 资源可控性:使用线程池可以避免创建大量线程而导致内存的消耗




     
  • 提高响应速度:线程池地创建实际上是很消耗时间和性能的,由线程池创建好有任务就运行,提升响应速度。




     
  • 便于管理:池化技术最突出的一个特点就是可以帮助我们对池子里的资源进行管理。由线程池统一分配和管理。




     

线程池的创建

​ 我们要用线程池来统一分配和管理我们的线程,那首先我们要创建一个线程池出来,还是有很多大牛已经帮我们写好了很多方面的代码的,Executors的工厂方法就给我们提供了创建多种不同线程池的方法。因为这个类只是一个创建对象的工厂,并没有涉及到很多的具体实现,所以我不会过于详细地去说明。

​ 老规矩,还是直接上代码吧。

public static ExecutorService newFixedThreadPool(int nThreads) {

      return new ThreadPoolExecutor(nThreads, nThreads,

                                    0L, TimeUnit.MILLISECONDS,

                                    new LinkedBlockingQueue<Runnable>());

  }

这里也就举出一个方法的例子来进行之后的讲解吧,我们可以看出,Executors只是个工厂而已,方法也只是来实例化不同的对象,实际上实例化出来的关键类就是ThreadPoolExecutor。现在我们就先来简单地对ThreadPoolExecutor构造函数内的每个参数进行解释一下吧。




  •  
  • corePoolSize(核心线程池大小):当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,当任务数大于核心线程数的时候就不会再创建。在这里要注意一点,线程池刚创建的时候,其中并没有创建任何线程,而是等任务来才去创建线程,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法 ,这样才会预先创建好corePoolSize个线程或者一个线程。




     
  • maximumPoolSize(线程池最大线程数):线程池允许创建的最大线程数,如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界队列,此参数就没有意义了。




     
  • keepAliveTime(线程活动保持时间):此参数默认在线程数大于corePoolSize的情况下才会起作用, 当线程的空闲时间达到keepAliveTime的时候就会终止,直至线程数目小于corePoolSize。不过如果调用了allowCoreThreadTimeOut方法,则当线程数目小于corePoolSize的时候也会起作用.




     
  • unit(keelAliveTime的时间单位):keelAliveTime的时间单位,一共有7种,在这里就不列举了。




     
  • workQueue(阻塞队列):阻塞队列,用来存储等待执行的任务,这个参数也是非常重要的,在这里简单介绍一下几个阻塞队列。

     



    •  
    • ArrayBlockingQueue:这是一个基于数组结构的有界阻塞队列,此队列按照FIFO的原则对元素进行排序。




       
    • LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按照FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()就是使用了这个队列。




       
    • SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool()就使用了这个队列。




       
    • PriorityBlockingQueue:一个具有优先级的无阻塞队列。




       



     
  • handler(饱和策略);当线程池和队列都满了,说明线程池已经处于饱和状态了,那么必须采取一种策略来处理还在提交过来的新任务。这个饱和策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。共有四种饱和策略提供,当然我们也可以选择自己实现饱和策略。

     



    •  
    • AbortPolicy:直接丢弃并且抛出RejectedExecutionException异常




       
    • CallerRunsPolicy:只用调用者所在线程来运行任务。




       
    • DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务。




       
    • DiscardPolicy:丢弃任务并且不抛出异常。




       



     

​

线程池的执行流程就用参考资料里的图介绍一下了,具体我们还是通过代码去讲解。


线程池流程.jpg

在上面我们简单的讲解了一下Executors这个工厂类里的工厂方法,并且讲述了一下创建线程池的一些参数以及它们的作用,当然上面的讲解并不是很深入,因为想要弄懂的话是需要持续地花时间去看去理解的,而博主自己也还是没有完全弄懂,不过博主的学习方法是先学了个大概,再回头来看看之前的知识点,可能会更加好理解,所以我们接着往下面讲吧。

ThreadPoolExecutor源码分析

​ 在上面我们就发现了,Executors的工厂方法主要就返回了ThreadPoolExecutor对象,至于另一个在这里暂时不讲,也就是说,要学习线程池,其实关键的还是得学会分析ThreadPoolExecutor这个对象里面的源码,我们接下来就会对ThreadPoolExecutor里的关键代码进行分析。

AtomicInteger ctl

​ ctl是主要的控制状态,是一个复合类型的变量,其中包括了两个概念。




  •  
  • workerCount:表示有效的线程数目




     
  • runState:线程池里线程的运行状态

     



     

我们来分析一下跟ctl有关的一些源代码吧,直接上代码

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

     //用来表示线程池数量的位数,很明显是29,Integer.SIZE=32

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

     //线程池最大数量,2^29 - 1

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

     // runState is stored in the high-order bits

     //我们可以看出有5种runState状态,证明至少需要3位来表示runState状态

     //所以高三位就是表示runState了

     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; }

     //用于存放线程任务的阻塞队列

     private final BlockingQueue<Runnable> workQueue;

     //重入锁

     private final ReentrantLock mainLock = new ReentrantLock();

     //线程池当中的线程集合,只有当拥有mainLock锁的时候,才可以进行访问

     private final HashSet<Worker> workers = new HashSet<Worker>();

     //等待条件支持终止

     private final Condition termination = mainLock.newCondition();

     //创建新线程的线程工厂

     private volatile ThreadFactory threadFactory;

     //饱和策略

     private volatile RejectedExecutionHandler handler;



  1.  
  2. CAPACITY

     

    在这里我们讲一下这个线程池最大数量的计算吧,因为这里涉及到源码以及位移之类的操作,我感觉大多数人都还是不太会这个,因为我一开始看的时候也是不太会的。




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

从代码我们可以看出,是需要1往左移29位,然后再减去1,那个1往左移29位是怎么计算的呢?

     1 << COUNT_BITS

      ​

      1的32位2进制是

      00000000 00000000 00000000 00000001

      ​

      左移29位的话就是

      00100000 00000000 00000000 00000000

      ​

      再进行减一的操作

      000 11111 11111111 11111111 11111111

      ​

      也就是说线程池最大数目就是

      000 11111 11111111 11111111 11111111

2.runState

 

正数的原码、反码、补码都是一样的


在计算机底层,是用补码来表示的


 
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;



  •  
  • RUNNING

     
     

    可以接受新任务并且处理已经在阻塞队列的任务


    高3位全部是1的话,就是RUNNING状态


     



     
-1 << COUNT_BITS

这里是-1往左移29位,稍微有点不一样,-1的话需要我们自己算出补码来

          ​

-1的原码

10000000 00000000 00000000 00000001

          ​

-1的反码,负数的反码是将原码除符号位以外全部取反

11111111 11111111 11111111 11111110

          ​

-1的补码,负数的补码就是将反码+1

11111111 11111111 11111111 11111111

          ​

关键了,往左移29位,所以高3位全是1就是RUNNING状态

111 00000 00000000 00000000 00000000



  •  
  • SHUTDOWN

     
     

    不接受新任务,但是处理已经在阻塞队列的任务


    高3位全是0,就是SHUTDOWN状态


     



     
0 << COUNT_BITS

          ​

0的表示

00000000 00000000 00000000 00000000

          ​

往左移29位

00000000 00000000 00000000 00000000



  •  
  • STOP

     
     

    不接受新任务,也不处理阻塞队列里的任务,并且会中断正在处理的任务


    所以高3位是001,就是STOP状态


     



     
1 << COUNT_BITS

          ​

1的表示

00000000 00000000 00000000 00000001

          ​

往左移29位

00100000 00000000 00000000 00000000



  •  
  • TIDYING

     
     

    所有任务都被中止,workerCount是0,线程状态转化为TIDYING并且调用terminated()钩子方法


    所以高3位是010,就是TIDYING状态


     



     
2 << COUNT_BITS

          ​

2的32位2进制

00000000 00000000 00000000 00000010

          ​

往左移29位

01000000 00000000 00000000 00000000



  •  
  • TERMINATED

     
     

    terminated()钩子方法已经完成


    所以高3位是110,就是TERMINATED状态


     



     
3 << COUNT_BITS

          ​

3的32位2进制

00000000 00000000 00000000 00000011

          ​

往左移29位

11000000 00000000 00000000 00000000

3.部分方法介绍




  •  
  • runStateOf(int c)


     
 

实时获取runState的方法


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

~是按位取反的意思

&是按位与的意思

          ​

而CAPACITY是,高位3个0,低29位都是1,所以是

000 11111 11111111 11111111 11111111

          ​

取反的话就是

111 00000 00000000 00000000 00000000

          ​

传进来的c参数与取反的CAPACITY进行按位与操作

1、低位29个0进行按位与,还是29个0

2、高位3个1,既保持c参数的高3位

既高位保持原样,低29位都是0,这也就获得了线程池的运行状态runState



  •  
  • workerCountOf(int c)


     
 

获取线程池的当前有效线程数目


 
private static int workerCountOf(int c)  { return c & CAPACITY; }
CAPACITY的32位2进制是

000 11111 11111111 11111111 11111111

          ​

用入参c跟CAPACITY进行按位与操作

1、低29位都是1,所以保留c的低29位,也就是有效线程数

2、高3位都是0,所以c的高3位也是0

          ​

这样获取出来的便是workerCount的值



  •  
  • ctlOf(int rs, int wc)

     
     

    原子整型变量ctl的初始化方法


     



     
//结合这几句代码来看

private static final int RUNNING    = -1 << COUNT_BITS;

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

          ​

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

111 00000 00000000 00000000 00000000

          ​

ctlOf是将rs和wc进行按位或的操作

          ​

初始化的时候是将RUNNING和0进行按位或

0的32位2进制是

00000000 00000000 00000000 00000000

          ​

所以初始化的ctl是

111 00000 00000000 00000000 00000000

核心方法源码分析




  1.  
  2. execute(Runnable command)方法


     
      public void execute(Runnable command) {

          //需要执行的任务command为空,抛出空指针异常

          if (command == null)  // 1

              throw new NullPointerException();

          /*

          *执行的流程实际上分为三步

          *1、如果运行的线程小于corePoolSize,以用户给定的Runable对象新开一个线程去执行

          *  并且执行addWorker方法会以原子性操作去检查runState和workerCount,以防止当返回false的

          *  时候添加了不应该添加的线程

          *2、 如果任务能够成功添加到队列当中,我们仍需要对添加的线程进行双重检查,有可能添加的线程在前

          *  一次检查时已经死亡,又或者在进入该方法的时候线程池关闭了。所以我们需要复查状态,并有有必

          *  要的话需要在停止时回滚入列操作,或者在没有线程的时候新开一个线程

          *3、如果任务无法入列,那我们需要尝试新增一个线程,如果新建线程失败了,我们就知道线程可能关闭了

          *  或者饱和了,就需要拒绝这个任务

          *

          */

          //获取线程池的控制状态

          int c = ctl.get();  // 2

          //通过workCountOf方法算workerCount值,小于corePoolSize

          if (workerCountOf(c) < corePoolSize) {

              //添加任务到worker集合当中

      if (addWorker(command, true)) 

                  return;  //成功返回

              //失败的话再次获取线程池的控制状态

              c = ctl.get();

          }

          /*

          *判断线程池是否正处于RUNNING状态

          *是的话添加Runnable对象到workQueue队列当中

          */

          if (isRunning(c) && workQueue.offer(command)) {  // 3

              //再次获取线程池的状态

              int recheck = ctl.get();

              //再次检查状态

              //线程池不处于RUNNING状态,将任务从workQueue队列中移除

              if (! isRunning(recheck) && remove(command))

                  //拒绝任务

                  reject(command);

              //workerCount等于0

              else if (workerCountOf(recheck) == 0)  // 4

                  //添加worker

                  addWorker(null, false);

          }

          //加入阻塞队列失败,则尝试以线程池最大线程数新开线程去执行该任务

      else if (!addWorker(command, false))  // 5 

              //执行失败则拒绝任务

              reject(command);

      }

我们来说一下上面这个代码的流程:

 

1、首先判断任务是否为空,空则抛出空指针异常


2、不为空则获取线程池控制状态,判断小于corePoolSize,添加到worker集合当中执行,

 



  •  
  • 如成功,则返回

     


     
  • 失败的话再接着获取线程池控制状态,因为只有状态变了才会失败,所以重新获取


    3、判断线程池是否处于运行状态,是的话则添加command到阻塞队列,加入时也会再次获取状态并且检测


    ​ 状态是否不处于运行状态,不处于的话则将command从阻塞队列移除,并且拒绝任务


    4、如果线程池里没有了线程,则创建新的线程去执行获取阻塞队列的任务执行


    5、如果以上都没执行成功,则需要开启最大线程池里的线程来执行任务,失败的话就丢弃


     

 

有时候再多的文字也不如一个流程图来的明白,所以还是画了个execute的流程图给大家方便理解。


execute执行流程.jpg

2.addWorker(Runnable firstTask, boolean core)

      private boolean addWorker(Runnable firstTask, boolean core) {

          //外部循环标记

          retry:

          //外层死循环

          for (;;) {

              //获取线程池控制状态

              int c = ctl.get();

              //获取runState

              int rs = runStateOf(c);

      ​

              // Check if queue empty only if necessary.

              /**

              *1.如果线程池runState至少已经是SHUTDOWN

              *2. 有一个是false则addWorker失败,看false的情况

              * - runState==SHUTDOWN,即状态已经大于SHUTDOWN了

              * - firstTask为null,即传进来的任务为空,结合上面就是runState是SHUTDOWN,但是

              *  firstTask不为空,代表线程池已经关闭了还在传任务进来

              * - 队列为空,既然任务已经为空,队列为空,就不需要往线程池添加任务了

              */

              if (rs >= SHUTDOWN &&  //runState大于等于SHUTDOWN,初始位RUNNING

                  ! (rs == SHUTDOWN &&  //runState等于SHUTDOWN

                     firstTask == null &&  //firstTask为null

                     ! workQueue.isEmpty()))  //workQueue队列不为空

                  return false;

      ​

              //内层死循环

              for (;;) {

                  //获取线程池的workerCount数量

                  int wc = workerCountOf(c);

                  //如果workerCount超出最大值或者大于corePoolSize/maximumPoolSize

                  //返回false

                  if (wc >= CAPACITY ||

                      wc >= (core ? corePoolSize : maximumPoolSize))

                      return false;

                  //通过CAS操作,使workerCount数量+1,成功则跳出循环,回到retry标记

                  if (compareAndIncrementWorkerCount(c))

                      break retry;

                  //CAS操作失败,再次获取线程池的控制状态

                  c = ctl.get();  // Re-read ctl

                  //如果当前runState不等于刚开始获取的runState,则跳出内层循环,继续外层循环

                  if (runStateOf(c) != rs)

                      continue retry;

                  // else CAS failed due to workerCount change; retry inner loop

                  //CAS由于更改workerCount而失败,继续内层循环

              }

          }

      ​

          //通过以上循环,能执行到这是workerCount成功+1了

          //worker开始标记

          boolean workerStarted = false;

          //worker添加标记

          boolean workerAdded = false;

          //初始化worker为null

          Worker w = null;

          try {

              //初始化一个当前Runnable对象的worker对象

              w = new Worker(firstTask);

              //获取该worker对应的线程

              final Thread t = w.thread;

              //如果线程不为null

              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.

                      //获取锁后再次检查,获取线程池runState

                      int rs = runStateOf(ctl.get());

      ​

                      //当runState小于SHUTDOWN或者runState等于SHUTDOWN并且firstTask为null

                      if (rs < SHUTDOWN ||

                          (rs == SHUTDOWN && firstTask == null)) {

                          //线程已存活

                          if (t.isAlive()) // precheck that t is startable

                              //线程未启动就存活,抛出IllegalThreadStateException异常

                              throw new IllegalThreadStateException();

                          //将worker对象添加到workers集合当中

                          workers.add(w);

                          //获取workers集合的大小

                          int s = workers.size();

                          //如果大小超过largestPoolSize

                          if (s > largestPoolSize)

                              //重新设置largestPoolSize

                              largestPoolSize = s;

                          //标记worker已经被添加

                          workerAdded = true;

                      }

                  } finally {

                      //释放锁

                      mainLock.unlock();

                  }

                  //如果worker添加成功

                  if (workerAdded) {

                      //启动线程

                      t.start();

                      //标记worker已经启动

                      workerStarted = true;

                  }

              }

          } finally {

              //如果worker没有启动成功

              if (! workerStarted)

                  //workerCount-1的操作

                  addWorkerFailed(w);

          }

          //返回worker是否启动的标记

          return workerStarted;

      }

我们也简单说一下这个代码的流程吧,还真的是挺难的,博主写的时候都停了好多次,想砸键盘的说:

 

1、获取线程池的控制状态,进行判断,不符合则返回false,符合则下一步


2、死循环,判断workerCount是否大于上限,或者大于corePoolSize/maximumPoolSize,没有的话则对workerCount+1操作,


3、如果不符合上述判断或+1操作失败,再次获取线程池的控制状态,获取runState与刚开始获取的runState相比,不一致则跳出内层循环继续外层循环,否则继续内层循环


4、+1操作成功后,使用重入锁ReentrantLock来保证往workers当中添加worker实例,添加成功就启动该实例。


 

接下来看看流程图来理解一下上面代码的一个执行流程


addWorker.jpg

3.addWorkerFailed(Worker w)

 

addWorker方法添加worker失败,并且没有成功启动任务的时候,就会调用此方法,将任务从workers中移除,并且workerCount做-1操作。


 
      private void addWorkerFailed(Worker w) {

          //重入锁

          final ReentrantLock mainLock = this.mainLock;

          //获取锁

          mainLock.lock();

          try {

              //如果worker不为null

              if (w != null)

                  //workers移除worker

                  workers.remove(w);

              //通过CAS操作,workerCount-1

              decrementWorkerCount();

              tryTerminate();

          } finally {

              //释放锁

              mainLock.unlock();

          }

      }

4.tryTerminate()

 

当对线程池执行了非正常成功逻辑的操作时,都会需要执行tryTerminate尝试终止线程池


 
      final void tryTerminate() {

          //死循环

          for (;;) {

              //获取线程池控制状态

              int c = ctl.get();

              /*

              *线程池处于RUNNING状态

              *线程池状态最小大于TIDYING

              *线程池==SHUTDOWN并且workQUeue不为空

              *直接return,不能终止

              */

              if (isRunning(c) ||

                  runStateAtLeast(c, TIDYING) ||

                  (runStateOf(c) == SHUTDOWN && ! workQueue.isEmpty()))

                  return;

              //如果workerCount不为0

              if (workerCountOf(c) != 0) { // Eligible to terminate

                  interruptIdleWorkers(ONLY_ONE);

                  return;

              }

      ​

              //获取线程池的锁

              final ReentrantLock mainLock = this.mainLock;

              //获取锁

              mainLock.lock();

              try {

                  //通过CAS操作,设置线程池状态为TIDYING

                  if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {

                      try {

                          terminated();

                      } finally {

                          //设置线程池的状态为TERMINATED

                          ctl.set(ctlOf(TERMINATED, 0));

                          //发送释放信号给在termination条件上等待的线程

                          termination.signalAll();

                      }

                      return;

                  }

              } finally {

                  //释放锁

                  mainLock.unlock();

              }

              // else retry on failed CAS

          }

      }

5.runWorker(Worker w)

 

该方法的作用就是去执行任务


 
final void runWorker(Worker w) {

      //获取当前线程

      Thread wt = Thread.currentThread();

      //获取worker里的任务

      Runnable task = w.firstTask;

      //将worker实例的任务赋值为null

      w.firstTask = null;

      /*

      *unlock方法会调用AQS的release方法

      *release方法会调用具体实现类也就是Worker的tryRelease方法

      *也就是将AQS状态置为0,允许中断

      */

      w.unlock(); // allow interrupts

      //是否突然完成

      boolean completedAbruptly = true;

      try {

          //worker实例的task不为空,或者通过getTask获取的不为空

          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

              /*

              *获取线程池的控制状态,至少要大于STOP状态

              *如果状态不对,检查当前线程是否中断并清除中断状态,并且再次检查线程池状态是否大于STOP

              *如果上述满足,检查该对象是否处于中断状态,不清除中断标记

              */

              if ((runStateAtLeast(ctl.get(), STOP) ||

                   (Thread.interrupted() &&

                    runStateAtLeast(ctl.get(), STOP))) &&

                  !wt.isInterrupted())

                  //中断改对象

                  wt.interrupt();

              try {

                  //执行前的方法,由子类具体实现

                  beforeExecute(wt, task);

                  Throwable thrown = null;

                  try {

                      //执行任务

                      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 {

                      //执行完后调用的方法,也是由子类具体实现

                      afterExecute(task, thrown);

                  }

              } finally {//执行完后

                  //task设置为null

                  task = null;

                  //已完成任务数+1

                  w.completedTasks++;

                  //释放锁

                  w.unlock();

              }

          }

          completedAbruptly = false;

      } finally {

          //处理并退出当前worker

          processWorkerExit(w, completedAbruptly);

      }

  }

接下来我们用文字来说明一下执行任务这个方法的具体逻辑和流程。

 



  1.  
  2. 首先在方法一进来,就执行了w.unlock(),这是为了将AQS的状态改为0,因为只有getState() >= 0的时候,线程才可以被中断;

     


     
  3. 判断firstTask是否为空,为空则通过getTask()获取任务,不为空接着往下执行

     


     
  4. 判断是否符合中断状态,符合的话设置中断标记

     


     
  5. 执行beforeExecute(),task.run(),afterExecute()方法


     
  6. 任何一个出异常都会导致任务执行的终止;进入processWorkerExit来退出任务


     
  7. 正常执行的话会接着回到步骤2


     

 

附上一副简单的流程图:


runWorker.jpg

6.getTask()

在上面的runWorker方法当中我们可以看出,当firstTask为空的时候,会通过该方法来接着获取任务去执行,那我们就看看获取任务这个方法到底是怎么样的?

      private Runnable getTask() {

          //标志是否获取任务超时

          boolean timedOut = false; // Did the last poll() time out?

      ​

          //死循环

          for (;;) {

              //获取线程池的控制状态

              int c = ctl.get();

              //获取线程池的runState

              int rs = runStateOf(c);

      ​

              // Check if queue empty only if necessary.

              /*

              *判断线程池的状态,出现以下两种情况

              *1、runState大于等于SHUTDOWN状态

              *2、runState大于等于STOP或者阻塞队列为空

              *将会通过CAS操作,进行workerCount-1并返回null

              */

              if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {

                  decrementWorkerCount();

                  return null;

              }

      ​

              //获取线程池的workerCount

              int wc = workerCountOf(c);

      ​

              // Are workers subject to culling?

              /*

              *allowCoreThreadTimeOut:是否允许core Thread超时,默认false

              *workerCount是否大于核心核心线程池

              */

              boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

      ​

              /*

              *1、wc大于maximumPoolSize或者已超时

              *2、队列不为空时保证至少有一个任务

              */

              if ((wc > maximumPoolSize || (timed && timedOut))

                  && (wc > 1 || workQueue.isEmpty())) {

                  /*

                  *通过CAS操作,workerCount-1

                  *能进行-1操作,证明wc大于maximumPoolSize或者已经超时

                  */

                  if (compareAndDecrementWorkerCount(c))

                      //-1操作成功,返回null

                      return null;

                  //-1操作失败,继续循环

                  continue;

              }

      ​

              try {

                  /*

                  *wc大于核心线程池

                  *执行poll方法

                  *小于核心线程池

                  *执行take方法

                  */

                  Runnable r = timed ?

                      workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :

                  workQueue.take();

                  //判断任务不为空返回任务

                  if (r != null)

                      return r;

                  //获取一段时间没有获取到,获取超时

                  timedOut = true;

              } catch (InterruptedException retry) {

                  timedOut = false;

              }

          }

      }

还是文字解说一下上面的代码逻辑和流程:

 



  1.  
  2. 获取线程池控制状态和runState,判断线程池是否已经关闭或者正在关闭,是的话则workerCount-1操作返回null

     


     
  3. 获取workerCount判断是否大于核心线程池

     


     
  4. 判断workerCount是否大于最大线程池数目或者已经超时,是的话workerCount-1,-1成功则返回null,不成功则回到步骤1重新继续

     


     
  5. 判断workerCount是否大于核心线程池,大于则用poll方法从队列获取任务,否则用take方法从队列获取任务


     
  6. 判断任务是否为空,不为空则返回获取的任务,否则回到步骤1重新继续


     

 

接下来依然有一副流程图:


getTask.jpg

7.processWorkerExit

明显的,在执行任务当中,会去获取任务进行执行,那既然是执行任务,肯定就会有执行完或者出现异常中断执行的时候,那这时候肯定也会有相对应的操作,至于具体操作是怎么样的,我们还是直接去看源码最实际。

     private void processWorkerExit(Worker w, boolean completedAbruptly) {

          /*

          *completedAbruptly:在runWorker出现,代表是否突然完成的意思

          *也就是在执行任务过程当中出现异常,就会突然完成,传true

          *

          *如果是突然完成,需要通过CAS操作,workerCount-1

          *不是突然完成,则不需要-1,因为getTask方法当中已经-1

          *

          *下面的代码注释貌似与代码意思相反了

          */

          if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted

              decrementWorkerCount();

      ​

          //生成重入锁

          final ReentrantLock mainLock = this.mainLock;

          //获取锁

          mainLock.lock();

          try {

              //线程池统计的完成任务数completedTaskCount加上worker当中完成的任务数

              completedTaskCount += w.completedTasks;

              //从HashSet<Worker>中移除

              workers.remove(w);

          } finally {

              //释放锁

              mainLock.unlock();

          }

      ​

          //因为上述操作是释放任务或线程,所以会判断线程池状态,尝试终止线程池

          tryTerminate();

      ​

          //获取线程池的控制状态

          int c = ctl.get();

          //判断runState是否小鱼STOP,即是RUNNING或者SHUTDOWN

          //如果是RUNNING或者SHUTDOWN,代表没有成功终止线程池

          if (runStateLessThan(c, STOP)) {

              /*

              *是否突然完成

              *如若不是,代表已经没有任务可获取完成,因为getTask当中是while循环

              */

              if (!completedAbruptly) {

                  /*

                  *allowCoreThreadTimeOut:是否允许core thread超时,默认false

                  *min-默认是corePoolSize

                  */

                  int min = allowCoreThreadTimeOut ? 0 : corePoolSize;

                  //允许core thread超时并且队列不为空

                  //min为0,即允许core thread超时,这样就不需要维护核心核心线程池了

                  //如果workQueue不为空,则至少保持一个线程存活

                  if (min == 0 && ! workQueue.isEmpty())

                      min = 1;

                  //如果workerCount大于min,则表示满足所需,可以直接返回

                  if (workerCountOf(c) >= min)

                      return; // replacement not needed

              }

              //如果是突然完成,添加一个空任务的worker线程--这里我也不太理解

              addWorker(null, false);

          }

      }
 



  1.  
  2. 首先判断线程是否突然终止,如果是突然终止,通过CAS,workerCount-1


     
  3. 统计线程池完成任务数,并将worker从workers当中移除


     
  4. 判断线程池状态,尝试终止线程池

     


     
  5. 线程池没有成功终止
     



    •  
    • 判断是否突然完成任务,不是则进行下一步,是则进行第三步

       


       
    • 如允许核心线程超时,队列不为空,则至少保证一个线程存活

       


       
    • 添加一个空任务的worker线程


       



     

 

Worker内部类

​ 我们在上面已经算是挺详细地讲了线程池执行任务execute的执行流程和一些细节,在上面频繁地出现了一个字眼,那就是worker实例,那么这个worker究竟是什么呢?里面都包含了一些什么信息,以及worker这个任务究竟是怎么执行的呢?

​ 我们就在这个部分来介绍一下吧,还是直接上源码:

 

我们可以看到Worker内部类继承AQS同步器并且实现了Runnable接口,所以Worker很明显就是一个可执行任务并且又可以控制中断、起到锁效果的类。


 
  private final class Worker

          extends AbstractQueuedSynchronizer

          implements Runnable

      {

          /**

           * This class will never be serialized, but we provide a

           * serialVersionUID to suppress a javac warning.

           */

          private static final long serialVersionUID = 6138294804551838833L;

  ​

          /** 工作线程,如果工厂失败则为空. */

          final Thread thread;

          /** 初始化任务,有可能为空 */

          Runnable firstTask;

          /** 已完成的任务计数 */

          volatile long completedTasks;

  ​

          /**

           * 创建并初始化第一个任务,使用线程工厂来创建线程

           * 初始化有3步

           *1、设置AQS的同步状态为-1,表示该对象需要被唤醒

           *2、初始化第一个任务

           *3、调用ThreadFactory来使自身创建一个线程,并赋值给worker的成员变量thread

           */

          Worker(Runnable firstTask) {

              setState(-1); // inhibit interrupts until runWorker

              this.firstTask = firstTask;

              this.thread = getThreadFactory().newThread(this);

          }

  ​

    //重写Runnable的run方法

          /** Delegates main run loop to outer runWorker  */

          public void run() {

              //调用ThreadPoolExecutor的runWorker方法

              runWorker(this);

          }

  ​

          // Lock methods

          //

          // The value 0 represents the unlocked state.

          // The value 1 represents the locked state.

    //代表是否独占锁,0-非独占  1-独占

          protected boolean isHeldExclusively() {

              return getState() != 0;

          }

    //重写AQS的tryAcquire方法尝试获取锁

          protected boolean tryAcquire(int unused) {

           //尝试将AQS的同步状态从0改为1

              if (compareAndSetState(0, 1)) {

               //如果改变成,则将当前独占模式的线程设置为当前线程并返回true

                  setExclusiveOwnerThread(Thread.currentThread());

                  return true;

              }

              //否则返回false

              return false;

          }

  ​

    //重写AQS的tryRelease尝试释放锁

          protected boolean tryRelease(int unused) {

           //设置当前独占模式的线程为null

              setExclusiveOwnerThread(null);

              //设置AQS同步状态为0

              setState(0);

              //返回true

              return true;

          }

  ​

    //获取锁

          public void lock()        { acquire(1); }

          //尝试获取锁

          public boolean tryLock()  { return tryAcquire(1); }

          //释放锁

          public void unlock()      { release(1); }

          //是否被独占

          public boolean isLocked() { return isHeldExclusively(); }

  ​

          void interruptIfStarted() {

              Thread t;

              if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {

                  try {

                      t.interrupt();

                  } catch (SecurityException ignore) {

                  }

              }

          }

  }

小结

写这个线程池就真的是不容易了,历时两个星期,中途有很多的地方不懂,而且《Java并发编程的艺术》的这本书当中对线程池的介绍其实并不算多,所以自己看起来也挺痛苦的,还经常会看了这个方法就不知道为什么要调用这个以及调用这个方法是出何用意。而且在这学习的过程当中,有在怀疑自己的学习方法对不对,因为也有人跟我说不需要一句句去看去分析源码,只需要知道流程就可以了,但是后来还是想想按照自己的学习路线走,多读源码总是有好处的,在这里我也给程序猿一些建议,有自己的学习方法的时候,按照自己的方式坚定走下去。

参考资料

方腾飞:《Java并发编程的艺术》

 

如需转载,请务必注明出处,毕竟一块块搬砖也不是容易的事情。


 

 


转载:https://www.cnblogs.com/KingJack/p/9595621.html

推荐内容:
【原创】JAVA面试解析(有赞一面)
Java 最常见的 200+ 面试题汇总
Java 最常见的 200+ 面试题汇总
Java 文件流操作.
2年Java开发工作经验面试总结
最新Java技术
Java面试知识点之计算机网络篇(一)
Java修行之路
Java面试问题汇总
java后端面试

 

【并发】Java并发线程池底层原理详解与源码分析(下) 这里只会打印会前30个任务(10+10+10=30),由于在3s内核心线程和临时线程都在忙碌中,队列也满了,按照ThreadPoolExecutor默认的策略会抛出异常! 按照线程池的工作顺序,会先分配10个核心线程(1~10),再装满队列(11~20),最后分配临时线程(21~30);执行逻辑是核心线程和临时线程会先把“手头上”的任务处理完,才会去处理队列里的任务,这就是队列里的任务(11~20)最后打印的原因!!! 阅读详情

相关推荐

汽车工程师必看:从CAN到Ethernet,6种车载通信协议全解析(附应用场景对比)

本文全面解析了从CAN到Ethernet的6种车载通信协议,包括CAN、LIN、MOST、FlexRay等,并对比了它们的应用场景和技术特点。文章详细介绍了各协议的演进历程、技术优势及在现代汽车中的混合网络架构设计,为汽车工程师提供了实用的协议选型指南和未来通信技术发展趋势。

weixin_28683689的博客 491

Java并发之线程池ThreadPoolExecutor源码分析学习

线程池学习 以下所有内容以及源码分析都是基于JDK1.8的,请知悉。 我写博客就真的比较没有顺序了,这可能跟我的学习方式有关,我自己也觉得这样挺不好的,但是没办法说服自己去改变,所以也只能这样想到什么学什么了。 ​ 池化技术真的是一门在我看来非常牛逼的技术,因为它做到了在有限资源内实现了资源利用的最大化,这让我想到了一门课程,那就是运筹学,当时在上运筹学的时候就经常做这种类似的问题。 ​ 言归...

weixin_34290096的博客 281

centos7 python3.12.1 报错 No module named _ssl,坑我几个小时

网上的教程很多,但几乎是无效的,跟着我这教程,可以完美解决。

Amio_的博客 1万+

线程池的原理和使用

1.为什么需要线程池? Thread pools address two different problems: they usually * provide improved performance when executing large numbers of * asynchronous tasks, due to reduced per-task invocation overhead, * and they provide a means of bounding and managing t

Laiyer_的博客 270

Android 13/14/15 默认授权应用权限的实现方法

Android版本修改方式实现原理13/14修改Permission.java将所有权限标记为Normal权限15修改DeviceIdleController.java + DefaultPermissionGrantPolicy.java监听应用安装并主动授权。

m0_60898338的博客 2909

【高等数值分析】Krylov子空间方法

文章目录1. 预备理论1.1 Krylov 子空间1.2 最佳逼近1.2.1 方法一:最佳平方逼近1.2.2 方法二:假设 AAA 对称正定1.2.3 方法三:残差2范数2. 基底正交化2.1 Arnoldi 过程(CGS)2.2 改进 Arnoldi 过程(MGS)2.3 Lanczos过程3. 方程组求解3.1 全正交方法 (FOM)3.2 D-Lanczos方法3.3 广义极小残量法(GMRES)3.4 MINRES 方法 1. 预备理论 现在需要求解一个大规模稀疏方程组 Ax=bAx=bAx=b,可

Glooow的博客 1万+

【线程池】线程池的ctl属性详解

线程池的ctl属性详解

小七的博客 2419

CPG控制网络入门

从线性变换开始推导

妖米的博客 5044

线程池源码解析

1. 线程池 Java引入Excutor框架将任务的提交和执行进行解耦,只需要定义好任务,然后提交给线程池即可。 1.1 线程池的3个好处 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。 1.2 线程池实现类ThreadPoolExecutor的构造方法参数 构造方法

qq_41159942的博客 909

手把手教你解决Windows 10网络驱动器无法访问问题(附详细排查步骤)

本文详细指导Windows 10用户解决网络驱动器无法访问的常见问题。通过从现象快速定位到系统排查,提供了从检查网络连通性、修复凭据权限到调整高级设置的完整步骤,特别是针对“找不到网络路径”等错误,帮助用户高效恢复对共享资源的访问。

weixin_29233857的博客 517

MuJoCo 入门教程(八)Model仓库

一个物理仿真器的好坏取决于它所仿真的模型,而在像 MuJoCo 这样功能强大、建模选项众多的仿真器中,很容易创建出行为与预期不符的 "坏 "模型。MuJoCo Menagerie 的目标是为社区提供一个设计精良、开箱即用的模型集。更多信息,请访问 Menagerie 资源库。

机器人控制与仿真应用 1939

并发编程(七)-ThreadPoolExecutor源码分析(二)

如果允许核心线程超时回收,或者当前线程池中正在运行的 Worker 线程数大于核心线程数,则将 timed 设为 true,表示当前线程池中的 Worker 线程需要被回收。如果线程池的运行状态为SHUTDOWN以下,或者运行状态为SHUTDOWN且要添加的任务为null,则将新的工作线程添加到工作线程列表workers中,并更新最大线程池大小largestPoolSize。获取Worker对象中的锁,如果线程池处于STOP状态或者线程被中断并且线程池处于STOP状态或者线程被中断,则中断该线程。

流星1688 569

Python爬虫实战:深度解析携程/飞猪酒店评论API与大数据采集

本文详细介绍了如何通过Python爬虫技术获取携程和飞猪平台的酒店评论数据。我们涵盖了从API分析、反爬虫对策到数据存储和处理的完整流程。API逆向分析:通过浏览器开发者工具发现和分析API接口反爬虫对策:使用请求头随机化、IP代理、浏览器自动化等技术异步处理:使用asyncio提高爬取效率数据质量:实现数据清洗和质量检查机制系统架构:构建可扩展的分布式爬虫系统未来改进方向:机器学习识别验证码分布式爬虫架构实时数据流处理更智能的反反爬虫策略。

2201_76125261的博客 601

Java面试-ThreadPoolExecutor 源码解析与自定义线程池

《Java面试200问》系列聚焦ThreadPoolExecutor源码解析与自定义线程池实践。文章以"拆解瑞士手表"为喻,系统剖析线程池核心架构,包括ctl字段的位运算设计、Worker类的线程封装机制、任务提交与执行流程(execute()/runWorker()/getTask())。通过源码级解读结合流程图解,帮助开发者深入理解线程池工作原理,掌握自定义线程池开发能力。文末还提供高频面试题解析,助力读者从"使用者"进阶为"制造者",提升Ja

千淘万漉虽辛苦,吹尽狂沙始到金 2万+

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Probability and Statistics - 4th edition

Probability and Statistics - 4th edition

2024年最新java并发编程之源码分析ThreadPoolExecutor线程池实现原理,31道Java面试题

Java面试核心知识点Java面试核心知识点cancelled.AND{@code}}@since 1.5*/2、ThreadPoolExecutors 内部数据结构与构造方法详解ThreadPoolExecutors的完整构造函数如下,从构造函数中能得出线程池最核心的属性。/**pool*/corePoolSize 核心线程数maximumPoolSize 最大线程数keepAliveTime 线程保持激活状态的时间,如果为0,永远处于激活状态。

wsjh156456的博客 951

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Java并发编程—线程池源码分析(面试总结)

Java并发编程—线程池源码分析(面试总结)

shang_0122的博客 881

C# WinForm 基于DirectShow.dll编写的程序,可边预览边保存视频,音频和图片

基于微软的DirectShow.dll的二次开发1.自动识别连接的摄像头跟麦克风且可以对其进行选择(默认选中第一个)2.可录制wmv和avi两种格式的视频并且都带有音频,avi可以录制的分辨率和帧数进行选择,wmv默认进行了淸晰度优化如不喜欢可去除代码中有相关提示3.可在预览点开之后进行拍照,也可边录制视频边拍照4.添加了视频和音频的压缩功能(酌情使用)这次二次开发发现C#对DirectShow的相关信息太少,这次做完也不敢藏私共享出来,还望大家指正

ThreadPoolExecutor:Java并发世界的七种武器!

在中,拒绝策略是当线程池和工作队列都已满时,如何处理新提交的任务。:默认策略,当任务被拒绝时,抛出异常。:当任务被拒绝时,由提交任务的线程来执行该任务。:当任务被拒绝时,直接丢弃该任务。:当任务被拒绝时,丢弃工作队列中最旧的任务,然后重新提交新任务。如果你需要自定义拒绝策略,可以通过实现接口来自定义处理逻辑。@Override// 自定义处理逻辑System.out.println("任务被拒绝: " + r.toString());// 可以选择记录日志、重试提交任务等操作然后在创建。

赵KK的博客 760

面试官:熟悉Java并发吗,谈谈对JUC线程池ThreadPoolExecutor的认识吧

前提 很早之前就打算看一次JUC线程池ThreadPoolExecutor的源码实现,由于近段时间比较忙,一直没有时间整理出源码分析的文章。之前在分析扩展线程池实现可回调的Future时候曾经提到并发大师Doug Lea在设计线程池ThreadPoolExecutor的提交任务的顶层接口Executor只有一个无状态的执行方法: public interface Executor { void execute(Runnable command); } 关键属性 源码分析 先分析..

m0_67698950的博客 253

并发编程-线程池详解(ThreadPoolExecutor源码分析)

线程池详解 一、为什么要有线程池? 当只有线程的时候,会有几个很明显的缺点: 1、来一个任务创建一个线程,线程创建的数量不可控; 2、频繁的创建和销毁线程,会消耗很多资源; 所以,就有了”线程池“。 它的优势: 1、限流 -> 控制线程数量 2、降低频繁创建和销毁线程 3、对于任务的响应速度更快(不再需要创建线程,而是复用已有的线程,所以就更快啦) 二、JAVA中提供的线程池 Executo...

cfy1024的博客 510

java并发编程之源码分析ThreadPoolExecutor线程池实现原理(1)

面试前的“练手”还是很重要的,所以开始面试之前一定要准备好啊,不然也是耽搁面试官和自己的时间。我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!!thrown = x;throw x;

2401_83916030的博客 276

threadpoolexecutor 不运行_面经手册 第21篇手写线程池,对照学习ThreadPoolExecutor线程池实现原理!...

持续坚持原创输出,点击蓝字关注我吧作者:小傅哥博客:https://bugstack.cn❝沉淀、分享、成长,让自己和他人都能有所收获!?❞目录一、前言二、面试题三、线程池讲解1. 先看个例子2. 手写一个线程池3. 线程池源码分析四、总结五、系列推荐一、前言人看手机,机器学习!正好是2020年,看到这张图还是蛮有意思的。以前小时候总会看到一些科技电影,讲到机器人会怎样怎样,但没想到人似...

weixin_39990558的博客 285

java多线程系列:ThreadPoolExecutor源码分析,java基础面试笔试题

我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。 扫描二维码或搜索下图红色VX号,加VX好友,拉你进【程序员面试学习交流群】免费领取。也欢迎各位一起在群里探讨技术。 推荐文章:Java 面试知识点解析;Mysql优化技巧(数据库设计、命名规范、索引优化 前言 这篇主要讲述ThreadPoolExecutor的源码分析,贯穿类的创...

agoodcoder777的博客 224

Java线程池使用和源码分析,java高级面试笔试题

我总结出了很多互联网公司的面试题及答案,并整理成了文档,以及各种学习的进阶学习资料,免费分享给大家。 扫描二维码或搜索下图红色VX号,加VX好友,拉你进【程序员面试学习交流群】免费领取。也欢迎各位一起在群里探讨技术。 推荐文章:Java 面试知识点解析;Mysql优化技巧(数据库设计、命名规范、索引优化 1.为什么使用线程池 在多线程编程中一项很重要的功能就是执行任务,而执行任务...

ccc_ccc8的博客 199

【Java并发编程】从源码分析几道必问线程池的面试题?

点击蓝色字关注我们!引言上一篇文章我们有介绍过线程池的一个基本执行流程《【Java并发编程】面试必备之线程池》以及它的7个核心参数,以及每个参数的作用、以及如何去使用线程池 还留了几个小...

root 130

并发工具四之线程池

线程池

qq_35576976的博客 541

liquid-glass 性能调优指南:filterResolution、mapSize 与多实例渲染成本,3步讲清取舍

liquid-glass 是一个零依赖的 React 玻璃透镜(Liquid Glass)组件,让网页内容在 Chrome、Safari、Firefox 中实时折射。🔍 本文只讲性能:`filterResolution`、`mapSize` 与多实例渲染这三类开销,帮你在不牺牲质感的前提下,让透镜稳定跑满 60fps。 [![liquid-glass 液态玻璃折射效果演示:玻璃透镜弯折页面真实

gitblog_00236的博客 207
上一篇: JDK1.8源码(七)——java.util.HashMap 类,java面试题,java基础笔试题,BAT
下一篇: Java高级篇(一)——线程,java面试题,java基础笔试题,BAT
ccc_ccc8
博客等级 码龄8年 8粉丝 50原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值