揭秘Java线程池的真相

版权声明:本文为博主原创文章,如需转载请标明出去。 https://blog.csdn.net/sujin_/article/details/83690425

线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中并等待下一次分配任务。

线程池的使用场景

场景一:

一个业务逻辑有很多次的循环,每次循环之间没有影响,比如验证1万条url路径是否存在,正常情况要循环1万次,逐个去验证每一条URL, 这样效率会很低,假设验证一条需要1分钟,总共就需要1万分钟,有点恐怖。这时可以用多线程,将1万条URL分成50等份,开50个线程,没个线程只需验证200条,这样所有的线程执行完是远小于1万分钟的。

场景二:

需要知道一个任务的执行进度,比如我们常看到的进度条,实现方式可以是在任务中加入一个整型属性变量(这样不同方法可以共享),任务执行一定程度就给变量值加1,另外开一个线程按时间间隔不断去访问这个变量,并反馈给用户。

总之使用多线程就是为了充分利用cpu的资源,提高程序执行效率,当你发现一个业务逻辑执行效率特别低,耗时特别长,就可以考虑使用多线程。不过CPU执行哪个线程的时间和顺序是不确定的,即使设置了线程的优先级,因此使用多线程的风险也是比较大的,会出现很多预料不到的问题,一定要多熟悉概念,多构造不同的场景去测试才能够掌握。

使用线程池的好处

1.降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的损耗。

2.提供响应速度。当任务到达时,任务可以不需要等待线程创建就能够立即执行。

3.提供线程的可管控性。合理利用线程池,必须了解它的实现原理。线程是稀缺资源,如果无限制的使用,不仅会消耗系统资源,还会降低系统的稳定性。使用线程池可以统一分配、调优和监控。

线程池的实现原理和工作机制

当向线程提交一个任务之后,线程池是如何处理这个任务的呢,让我们一起来揭秘线程池的处理流程,如下图所示:

当提交一个新任务到线程池时,流程如下:

  1. 判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。如果都在执行,则进入下一个流程。
  2. 判断工作队列是否已经满了。如果还没有满,则将新提交的任务存储在这个工作队列里面。如果满了,则进入下一个流程。
  3. 判断线程池的线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。如果已经满了,则交给饱和策略来处理这个任务。

ThreadPoolExecutor执行execute方法的主要处理流程示意图如下所示:

ThreadPoolExecutor执行execute存在四大场景:

  1. 如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(需要获取全局锁)。
  2. 如果运行的线程等于或多于corePoolSize,则将任务加入BlockingQueue。
  3. 如果无法将任务加入到BlockingQueue(队列已满时),则创建新的线程来处理(需要获取全局锁)。
  4. 如果创建新线程将使当前运行的线程超出maximumPoolSize,任务将被拒绝,并调用RejectedExecutionHandler.rejectedExecution()方法。

Worker:ThreadPoolExecutor的一个内部类,实现了AbstractQueuedSynchronizer抽象类。

全局锁:即允许一个线程调用执行的时候让 其它线程处于等待状态.。

ThreadPoolExecutor采用以上场景,是为了执行execute()方法时,尽可能地避免获取全局锁(一个严重的可伸缩瓶颈)。当ThreadPoolExecutor运行的线程大于或等于corePoolSize时,几乎所有的execute()方法都将会调用第二场景,而且第二场景是不需要获取全局锁的。 

源码分析:上面的流程分析让我们很直观的了解了线程池的工作原理。再让我们通过原理了解一下它的实现原理

 public void execute(Runnable command) {  
    if (command == null)  throw new NullPointerException();  
    //--如果核心线程已经用尽( poolSize >= corePoolSize ), 进入if分支  
    //--核心线程没用完,立即执行线程,不进入if分支(!addIfUnderCorePoolSize(command))  
    if (poolSize >= corePoolSize /*核心线程没空余*/ || !addIfUnderCorePoolSize(command)/*核心线程有空余*/)
    {  
    //--如果核心线程已经用尽, 并且runState == RUNNING, 则添加command到workQueue中去  
    if (runState == RUNNING && workQueue.offer(command)) {  
    if (runState != RUNNING || poolSize == 0)  
     //如果线程池状态不为RUNNING,或者正在工作的线程个数为0,则尽量保证添加的Command被处理(Reject)  
      ensureQueuedTaskHandled(command);  
    }  
    //--如果workQueue已满,则尝试直接执行线程(需要保证当前运行线程<maximumPoolSize,感觉好像插队了一样,不公平)  
    else if (!addIfUnderMaximumPoolSize(command))  
    //如果poolSize>=maximumPoolSize,则拒绝(通过抛出运行时异常)  
        reject(command); // is shutdown or saturated       
    }  
}  

工作线程:线程池创建线程时,会将线程封装成工作线程Worker,Worker在执行完任务后,还会循环获取工作队列里的任务来执行。我们可以从Worker类的run()方法看到这点。

//Worker  
public void run() {  
    try {  
      Runnable task = firstTask;  
      firstTask = null;  
          //task = getTask()就是尝试从队列中取线程来执行  
          while (task != null || (task = getTask()) != null) {  
              runTask(task);  
            task = null;  
          }  
      } finally {  
          workerDone(this);  
     }  
 } 

ThreadPoolExecutor中线程执行任务的示意图如下所示:

ThreadPoolExecutor中线程执行任务分为两种情况:

  1. 在执行execute()方法创建一个线程,会让这个线程执行当前任务。
  2. 在这个线程执行完上图1的任务之后,会反复从BlockingQueue来获取任务来执行。

---------------------------------------------很多东西宁缺毋滥,流星的光芒短暂而灼热闪耀。---------------------------------------------

猜你喜欢

转载自blog.csdn.net/sujin_/article/details/83690425