乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      面經(jīng)手冊 · 第21篇《手寫線程池,對照學習ThreadPoolExecutor線程池實現(xiàn)原理!》

       小傅哥 2021-12-13


      作者:小傅哥
      博客:https://
      Github:https://github.com/fuzhengwei/CodeGuide/wiki

      沉淀、分享、成長,讓自己和他人都能有所收獲!😄

      一、前言

      人看手機,機器學習!

      正好是2020年,看到這張圖還是蠻有意思的。以前小時候總會看到一些科技電影,講到機器人會怎樣怎樣,但沒想到人似乎被娛樂化的東西,搞成了低頭族、大肚子!

      當意識到這一點時,其實非常懷念小時候。放假的早上跑出去,喊上三五個伙伴,要不下河摸摸魚、彈彈玻璃球、打打pia、跳跳房子!一天下來真的不會感覺累,但現(xiàn)在如果是放假的一天,你的娛樂安排,很多時候會讓頭很累!

      就像,你有試過學習一天英語頭疼,還是刷一天抖音頭疼嗎?或者玩一天游戲與打一天球!如果你意識到了,那么爭取放下一會手機,適當娛樂,鍛煉保持個好身體!

      二、面試題

      謝飛機,小記!,上次吃虧在線程上,這可能一次坑掉兩次嗎!

      謝飛機:你問吧,我準備好了!!!

      面試官:嗯,線程池狀態(tài)是如何設計存儲的?

      謝飛機:這!下一個,下一個!

      面試官:Worker 的實現(xiàn)類,為什么不使用 ReentrantLock 來實現(xiàn)呢,而是自己繼承AQS?

      謝飛機:我…!

      面試官:那你簡述下,execute 的執(zhí)行過程吧!

      謝飛機:再見!

      三、線程池講解

      1. 先看個例子

      ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
      threadPoolExecutor.execute(() -> {
          System.out.println("Hi 線程池!");
      });
      threadPoolExecutor.shutdown();
      
      // Executors.newFixedThreadPool(10);
      // Executors.newCachedThreadPool();
      // Executors.newScheduledThreadPool(10);
      // Executors.newSingleThreadExecutor();
      

      這是一段用于創(chuàng)建線程池的例子,相信你已經(jīng)用了很多次了。

      線程池的核心目的就是資源的利用,避免重復創(chuàng)建線程帶來的資源消耗。因此引入一個池化技術的思想,避免重復創(chuàng)建、銷毀帶來的性能開銷。

      那么,接下來我們就通過實踐的方式分析下這個池子的構造,看看它是如何處理線程的。

      2. 手寫一個線程池

      2.1 實現(xiàn)流程

      為了更好的理解和分析關于線程池的源碼,我們先來按照線程池的思想,手寫一個非常簡單的線程池。

      其實很多時候一段功能代碼的核心主邏輯可能并沒有多復雜,但為了讓核心流程順利運行,就需要額外添加很多分支的輔助流程。就像我常說的,為了保護手才把擦屁屁紙弄那么大!

      圖 21-1 線程池簡化流程

      關于圖 21-1,這個手寫線程池的實現(xiàn)也非常簡單,只會體現(xiàn)出核心流程,包括:

      1. 有n個一直在運行的線程,相當于我們創(chuàng)建線程池時允許的線程池大小。
      2. 把線程提交給線程池運行。
      3. 如果運行線程池已滿,則把線程放入隊列中。
      4. 最后當有空閑時,則獲取隊列中線程進行運行。

      2.2 實現(xiàn)代碼

      public class ThreadPoolTrader implements Executor {
      
          private final AtomicInteger ctl = new AtomicInteger(0);
      
          private volatile int corePoolSize;
          private volatile int maximumPoolSize;
      
          private final BlockingQueue<Runnable> workQueue;
      
          public ThreadPoolTrader(int corePoolSize, int maximumPoolSize, BlockingQueue<Runnable> workQueue) {
              this.corePoolSize = corePoolSize;
              this.maximumPoolSize = maximumPoolSize;
              this.workQueue = workQueue;
          }
      
          @Override
          public void execute(Runnable command) {
              int c = ctl.get();
              if (c < corePoolSize) {
                  if (!addWorker(command)) {
                      reject();
                  }
                  return;
              }
              if (!workQueue.offer(command)) {
                  if (!addWorker(command)) {
                      reject();
                  }
              }
          }
      
          private boolean addWorker(Runnable firstTask) {
              if (ctl.get() >= maximumPoolSize) return false;
      
              Worker worker = new Worker(firstTask);
              worker.thread.start();
              ctl.incrementAndGet();
              return true;
          }
      
          private final class Worker implements Runnable {
      
              final Thread thread;
              Runnable firstTask;
      
              public Worker(Runnable firstTask) {
                  this.thread = new Thread(this);
                  this.firstTask = firstTask;
              }
      
              @Override
              public void run() {
                  Runnable task = firstTask;
                  try {
                      while (task != null || (task = getTask()) != null) {
                          task.run();
                          if (ctl.get() > maximumPoolSize) {
                              break;
                          }
                          task = null;
                      }
                  } finally {
                      ctl.decrementAndGet();
                  }
              }
      
              private Runnable getTask() {
                  for (; ; ) {
                      try {
                          System.out.println("workQueue.size:" + workQueue.size());
                          return workQueue.take();
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                  }
              }
          }
      
          private void reject() {
              throw new RuntimeException("Error!ctl.count:" + ctl.get() + " workQueue.size:" + workQueue.size());
          }
      
          public static void main(String[] args) {
              ThreadPoolTrader threadPoolTrader = new ThreadPoolTrader(2, 2, new ArrayBlockingQueue<Runnable>(10));
      
              for (int i = 0; i < 10; i++) {
                  int finalI = i;
                  threadPoolTrader.execute(() -> {
                      try {
                          Thread.sleep(1500);
                      } catch (InterruptedException e) {
                          e.printStackTrace();
                      }
                      System.out.println("任務編號:" + finalI);
                  });
              }
          }
      
      }
      
      // 測試結果
      
      任務編號:1
      任務編號:0
      workQueue.size:8
      workQueue.size:8
      任務編號:3
      workQueue.size:6
      任務編號:2
      workQueue.size:5
      任務編號:5
      workQueue.size:4
      任務編號:4
      workQueue.size:3
      任務編號:7
      workQueue.size:2
      任務編號:6
      workQueue.size:1
      任務編號:8
      任務編號:9
      workQueue.size:0
      workQueue.size:0
      

      以上,關于線程池的實現(xiàn)還是非常簡單的,從測試結果上已經(jīng)可以把最核心的池化思想體現(xiàn)出來了。主要功能邏輯包括:

      • ctl,用于記錄線程池中線程數(shù)量。
      • corePoolSize、maximumPoolSize,用于限制線程池容量。
      • workQueue,線程池隊列,也就是那些還不能被及時運行的線程,會被裝入到這個隊列中。
      • execute,用于提交線程,這個是通用的接口方法。在這個方法里主要實現(xiàn)的就是,當前提交的線程是加入到worker、隊列還是放棄。
      • addWorker,主要是類 Worker 的具體操作,創(chuàng)建并執(zhí)行線程。這里還包括了 getTask() 方法,也就是從隊列中不斷的獲取未被執(zhí)行的線程。

      ,那么以上呢,就是這個簡單線程池實現(xiàn)的具體體現(xiàn)。但如果深思熟慮就會發(fā)現(xiàn)這里需要很多完善,比如:線程池狀態(tài)呢,不可能一直奔跑呀!?、線程池的鎖呢,不會有并發(fā)問題嗎?、線程池拒絕后的策略呢?,這些問題都沒有在主流程解決,也正因為沒有這些流程,所以上面的代碼才更容易理解。

      接下來,我們就開始分析線程池的源碼,與我們實現(xiàn)的簡單線程池參考對比,會更加容易理解😄!

      3. 線程池源碼分析

      3.1 線程池類關系圖

      圖 21-2 線程池類關系圖

      以圍繞核心類 ThreadPoolExecutor 的實現(xiàn)展開的類之間實現(xiàn)和繼承關系,如圖 21-2 線程池類關系圖。

      • 接口 Executor、ExecutorService,定義線程池的基本方法。尤其是 execute(Runnable command) 提交線程池方法。
      • 抽象類 AbstractExecutorService,實現(xiàn)了基本通用的接口方法。
      • ThreadPoolExecutor,是整個線程池最核心的工具類方法,所有的其他類和接口,為圍繞這個類來提供各自的功能。
      • Worker,是任務類,也就是最終執(zhí)行的線程的方法。
      • RejectedExecutionHandler,是拒絕策略接口,有四個實現(xiàn)類;AbortPolicy(拋異常方式拒絕)、DiscardPolicy(直接丟棄)、DiscardOldestPolicy(丟棄存活時間最長的任務)、CallerRunsPolicy(誰提交誰執(zhí)行)。
      • Executors,是用于創(chuàng)建我們常用的不同策略的線程池,newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadExecutor

      3.2 高3位與低29位

      圖 22-3 線程狀態(tài),高3位與低29位

      private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
      private static final int COUNT_BITS = Integer.SIZE - 3;
      private static final int CAPACITY   = (1 << COUNT_BITS) - 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;
      

      ThreadPoolExecutor 線程池實現(xiàn)類中,使用 AtomicInteger 類型的 ctl 記錄線程池狀態(tài)和線程池數(shù)量。在一個類型上記錄多個值,它采用的分割數(shù)據(jù)區(qū)域,高3位記錄狀態(tài),低29位存儲線程數(shù)量,默認 RUNNING 狀態(tài),線程數(shù)為0個。

      3.2 線程池狀態(tài)

      圖 22-4 線程池狀態(tài)流轉

      圖 22-4 是線程池中的狀態(tài)流轉關系,包括如下狀態(tài):

      • RUNNING:運行狀態(tài),接受新的任務并且處理隊列中的任務。
      • SHUTDOWN:關閉狀態(tài)(調(diào)用了shutdown方法)。不接受新任務,,但是要處理隊列中的任務。
      • STOP:停止狀態(tài)(調(diào)用了shutdownNow方法)。不接受新任務,也不處理隊列中的任務,并且要中斷正在處理的任務。
      • TIDYING:所有的任務都已終止了,workerCount為0,線程池進入該狀態(tài)后會調(diào) terminated() 方法進入TERMINATED 狀態(tài)。
      • TERMINATED:終止狀態(tài),terminated() 方法調(diào)用結束后的狀態(tài)。

      3.3 提交線程(execute)

      圖 22-5 提交線程流程圖

      public void execute(Runnable command) {
          if (command == null)
              throw new NullPointerException();
          int c = ctl.get();
          if (workerCountOf(c) < corePoolSize) {
              if (addWorker(command, true))
                  return;
              c = ctl.get();
          }
          if (isRunning(c) && workQueue.offer(command)) {
              int recheck = ctl.get();
              if (! isRunning(recheck) && remove(command))
                  reject(command);
              else if (workerCountOf(recheck) == 0)
                  addWorker(null, false);
          }
          else if (!addWorker(command, false))
              reject(command);
      }
      

      在閱讀這部分源碼的時候,可以參考我們自己實現(xiàn)的線程池。其實最終的目的都是一樣的,就是這段被提交的線程,啟動執(zhí)行加入隊列、決策策略,這三種方式。

      • ctl.get(),取的是記錄線程狀態(tài)和線程個數(shù)的值,最終需要使用方法 workerCountOf(),來獲取當前線程數(shù)量。`workerCountOf 執(zhí)行的是 c & CAPACITY 運算
      • 根據(jù)當前線程池中線程數(shù)量,與核心線程數(shù) corePoolSize 做對比,小于則進行添加線程到任務執(zhí)行隊列。
      • 如果說此時線程數(shù)已滿,那么則需要判斷線程池是否為運行狀態(tài) isRunning(c)。如果是運行狀態(tài)則把不能被執(zhí)行的線程放入線程隊列中。
      • 放入線程隊列以后,還需要重新判斷線程是否運行以及移除操作,如果非運行且移除,則進行拒絕策略。否則判斷線程數(shù)量為0后添加新線程。
      • 最后就是再次嘗試添加任務執(zhí)行,此時方法 addWorker 的第二個入?yún)⑹?false,最終會影響添加執(zhí)行任務數(shù)量判斷。如果添加失敗則進行拒絕策略。

      3.5 添加執(zhí)行任務(addWorker)

      圖 22-6 添加執(zhí)行任務邏輯流程

      private boolean addWorker(Runnable firstTask, boolean core)

      第一部分、增加線程數(shù)量

      retry:
      for (;;) {
          int c = ctl.get();
          int rs = runStateOf(c);
          // Check if queue empty only if necessary.
          if (rs >= SHUTDOWN &&
              ! (rs == SHUTDOWN &&
                 firstTask == null &&
                 ! workQueue.isEmpty()))
              return false;
          for (;;) {
              int wc = workerCountOf(c);
              if (wc >= CAPACITY ||
                  wc >= (core ? corePoolSize : maximumPoolSize))
                  return false;
              if (compareAndIncrementWorkerCount(c))
                  break retry;
              c = ctl.get();  // Re-read ctl
              if (runStateOf(c) != rs)
                  continue retry;
              // else CAS failed due to workerCount change; retry inner loop
          }
      }
      

      第一部分、創(chuàng)建啟動線程

      boolean workerStarted = false;
      boolean workerAdded = false;
      Worker w = null;
      try {
          w = new Worker(firstTask);
          final Thread t = w.thread;
          if (t != null) {
              final ReentrantLock mainLock = this.mainLock;
              mainLock.lock();
              try {
                  int rs = runStateOf(ctl.get());
                  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) {
                  t.start();
                  workerStarted = true;
              }
          }
      } finally {
          if (! workerStarted)
              addWorkerFailed(w);
      }
      return workerStarted;
      

      添加執(zhí)行任務的流程可以分為兩塊看,上面代碼部分是用于記錄線程數(shù)量、下面代碼部分是在獨占鎖里創(chuàng)建執(zhí)行線程并啟動。這部分代碼在不看鎖、CAS等操作,那么就和我們最開始手寫的線程池基本一樣了

      • if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty())),判斷當前線程池狀態(tài),是否為 SHUTDOWN、STOP、TIDYING、TERMINATED中的一個。并且當前狀態(tài)為 SHUTDOWN、且傳入的任務為 null,同時隊列不為空。那么就返回 false。
      • compareAndIncrementWorkerCount,CAS 操作,增加線程數(shù)量,成功就會跳出標記的循環(huán)體。
      • runStateOf(c) != rs,最后是線程池狀態(tài)判斷,決定是否循環(huán)。
      • 在線程池數(shù)量記錄成功后,則需要進入加鎖環(huán)節(jié),創(chuàng)建執(zhí)行線程,并記錄狀態(tài)。在最后如果判斷沒有啟動成功,則需要執(zhí)行 addWorkerFailed 方法,剔除到線程方法等操作。

      3.6 執(zhí)行線程(runWorker)

      final void runWorker(Worker w) {
          Thread wt = Thread.currentThread();
          Runnable task = w.firstTask;
          w.firstTask = null;
          w.unlock(); // 允許中斷
          boolean completedAbruptly = true;
          try {
              while (task != null || (task = getTask()) != null) 
                  w.lock();
                  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();
                      } finally {
                          afterExecute(task, thrown);
                      }
                  } finally {
                      task = null;
                      w.completedTasks++;
                      w.unlock();
                  }
              }
              completedAbruptly = false;
          } finally {
              processWorkerExit(w, completedAbruptly);
          }
      }
      

      其實,有了手寫線程池的基礎,到這也就基本了解了,線程池在干嘛。到這最核心的點就是 task.run() 讓線程跑起來。額外再附帶一些其他流程如下;

      • beforeExecute、afterExecute,線程執(zhí)行的前后做一些統(tǒng)計信息。
      • 另外這里的鎖操作是 Worker 繼承 AQS 自己實現(xiàn)的不可重入的獨占鎖。
      • processWorkerExit,如果你感興趣,類似這樣的方法也可以深入了解下。在線程退出時候workers做到一些移除處理以及完成任務數(shù)等,也非常有意思

      3.7 隊列獲取任務(getTask)

      如果你已經(jīng)開始閱讀源碼,可以在 runWorker 方法中,看到這樣一句循環(huán)代碼 while (task != null || (task = getTask()) != null)。這與我們手寫線程池中操作的方式是一樣的,核心目的就是從隊列中獲取線程方法。

      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.
              if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
                  decrementWorkerCount();
                  return null;
              }
              int wc = workerCountOf(c);
              // Are workers subject to culling?
              boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
              if ((wc > maximumPoolSize || (timed && timedOut))
                  && (wc > 1 || workQueue.isEmpty())) {
                  if (compareAndDecrementWorkerCount(c))
                      return null;
                  continue;
              }
              try {
                  Runnable r = timed ?
                      workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                      workQueue.take();
                  if (r != null)
                      return r;
                  timedOut = true;
              } catch (InterruptedException retry) {
                  timedOut = false;
              }
          }
      }
      
      • getTask 方法從阻塞隊列中獲取等待被執(zhí)行的任務,也就是一條條往出拿線程方法。
      • if (rs >= SHUTDOWN ...,判斷線程是否關閉。
      • wc = workerCountOf(c),wc > corePoolSize,如果工作線程數(shù)超過核心線程數(shù)量 corePoolSize 并且 workQueue 不為空,則增加工作線程。但如果超時未獲取到線程,則會把大于 corePoolSize 的線程銷毀掉。
      • timed,是 allowCoreThreadTimeOut 得來的。最終 timed 為 true 時,則通過阻塞隊列的poll方法進行超時控制。
      • 如果在 keepAliveTime 時間內(nèi)沒有獲取到任務,則返回null。如果為false,則阻塞。

      四、總結

      • 這一章節(jié)并沒有完全把線程池的所有知識點都介紹完,否則一篇內(nèi)容會有些臃腫。在這一章節(jié)我們從手寫線程池開始,逐步的分析這些代碼在Java的線程池中是如何實現(xiàn)的,涉及到的知識點也幾乎是我們以前介紹過的內(nèi)容,包括:隊列、CAS、AQS、重入鎖、獨占鎖等內(nèi)容。所以這些知識也基本是環(huán)環(huán)相扣的,最好有一些根基否則會有些不好理解。
      • 除了本章介紹的,我們還沒有講到線程的銷毀過程、四種線程池方法的選擇和使用、以及在CPU密集型任務、IO 密集型任務時該怎么配置。另外在Spring中也有自己實現(xiàn)的線程池方法。這些知識點都非常貼近實際操作。
      • 好了,今天的內(nèi)容先扯到這,后續(xù)的內(nèi)容陸續(xù)完善。如果以上內(nèi)容有錯字、流程缺失、或者不好理解以及描述錯誤,歡迎留言。互相學習、互相進步。

      五、系列推薦

      • Thread.start() ,它是怎么讓線程啟動的呢?
      • Thread 線程,狀態(tài)轉換、方法使用、原理分析
      • ReentrantLock之AQS原理分析和實踐使用
      • 什么是雙端隊列、延遲對列、阻塞隊列,全是知識盲區(qū)!
      • 90%的程序員,都沒用過多線程和鎖,怎么成為架構師?

        轉藏 分享 獻花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多