自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

阻塞隊列—DelayedWorkQueue源碼分析

開發(fā) 前端
隊列是先進先出的數(shù)據(jù)結(jié)構(gòu),就是先進入隊列的數(shù)據(jù),先被獲取。但是有一種特殊的隊列叫做優(yōu)先級隊列,它會對插入的數(shù)據(jù)進行優(yōu)先級排序,保證優(yōu)先級越高的數(shù)據(jù)首先被獲取,與數(shù)據(jù)的插入順序無關(guān)。

 前言

線程池運行時,會不斷從任務(wù)隊列中獲取任務(wù),然后執(zhí)行任務(wù)。如果我們想實現(xiàn)延時或者定時執(zhí)行任務(wù),重要一點就是任務(wù)隊列會根據(jù)任務(wù)延時時間的不同進行排序,延時時間越短地就排在隊列的前面,先被獲取執(zhí)行。

隊列是先進先出的數(shù)據(jù)結(jié)構(gòu),就是先進入隊列的數(shù)據(jù),先被獲取。但是有一種特殊的隊列叫做優(yōu)先級隊列,它會對插入的數(shù)據(jù)進行優(yōu)先級排序,保證優(yōu)先級越高的數(shù)據(jù)首先被獲取,與數(shù)據(jù)的插入順序無關(guān)。

實現(xiàn)優(yōu)先級隊列高效常用的一種方式就是使用堆。關(guān)于堆的實現(xiàn)可以查看《堆和二叉堆的實現(xiàn)和特性》

ScheduledThreadPoolExecutor線程池

ScheduledThreadPoolExecutor繼承自ThreadPoolExecutor,所以其內(nèi)部的數(shù)據(jù)結(jié)構(gòu)和ThreadPoolExecutor基本一樣,并在其基礎(chǔ)上增加了按時間調(diào)度執(zhí)行任務(wù)的功能,分為延遲執(zhí)行任務(wù)和周期性執(zhí)行任務(wù)。

ScheduledThreadPoolExecutor的構(gòu)造函數(shù)只能傳3個參數(shù)corePoolSize、ThreadFactory、RejectedExecutionHandler,默認maximumPoolSize為Integer.MAX_VALUE。

工作隊列是高度定制化的延遲阻塞隊列DelayedWorkQueue,其實現(xiàn)原理和DelayQueue基本一樣,核心數(shù)據(jù)結(jié)構(gòu)是二叉最小堆的優(yōu)先隊列,隊列滿時會自動擴容,所以offer操作永遠不會阻塞,maximumPoolSize也就用不上了,所以線程池中永遠會保持至多有corePoolSize個工作線程正在運行。

  1. public ScheduledThreadPoolExecutor(int corePoolSize, 
  2.                                    ThreadFactory threadFactory, 
  3.                                    RejectedExecutionHandler handler) { 
  4.     super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, 
  5.           new DelayedWorkQueue(), threadFactory, handler); 

 DelayedWorkQueue延遲阻塞隊列  


DelayedWorkQueue 也是一種設(shè)計為定時任務(wù)的延遲隊列,它的實現(xiàn)和DelayQueue一樣,不過是將優(yōu)先級隊列和DelayQueue的實現(xiàn)過程遷移到本身方法體中,從而可以在該過程當中靈活的加入定時任務(wù)特有的方法調(diào)用。

工作原理

DelayedWorkQueue的實現(xiàn)原理中規(guī)中矩,內(nèi)部維護了一個以RunnableScheduledFuture類型數(shù)組實現(xiàn)的最小二叉堆,初始容量是16,使用ReentrantLock和Condition實現(xiàn)生產(chǎn)者和消費者模式。

源碼分析

定義

DelayedWorkQueue 的類繼承關(guān)系如下:


其包含的方法定義如下:


成員屬性

  1. // 初始時,數(shù)組長度大小。 
  2. private static final int INITIAL_CAPACITY = 16;         
  3. // 使用數(shù)組來儲存隊列中的元素。 
  4. private RunnableScheduledFuture<?>[] queue =  new RunnableScheduledFuture<?>[INITIAL_CAPACITY];         
  5. // 使用lock來保證多線程并發(fā)安全問題。 
  6. private final ReentrantLock lock = new ReentrantLock();         
  7. // 隊列中儲存元素的大小 
  8. private int size = 0;         
  9. //特指隊列頭任務(wù)所在線程 
  10. private Thread leader = null;         
  11. // 當隊列頭的任務(wù)延時時間到了,或者有新的任務(wù)變成隊列頭時,用來喚醒等待線程 
  12. private final Condition available = lock.newCondition(); 

 DelayedWorkQueue是用數(shù)組來儲存隊列中的元素,核心數(shù)據(jù)結(jié)構(gòu)是二叉最小堆的優(yōu)先隊列,隊列滿時會自動擴容。

構(gòu)造函數(shù)

DelayedWorkQueue 是 ScheduledThreadPoolExecutor 的靜態(tài)類部類,默認只有一個無參構(gòu)造方法。

  1. static class DelayedWorkQueue extends AbstractQueue<Runnable> 
  2.         implements BlockingQueue<Runnable> { 
  3.  // ... 

 入隊方法

DelayedWorkQueue 提供了 put/add/offer(帶時間) 三個插入元素方法。我們發(fā)現(xiàn)與普通阻塞隊列相比,這三個添加方法都是調(diào)用offer方法。那是因為它沒有隊列已滿的條件,也就是說可以不斷地向DelayedWorkQueue添加元素,當元素個數(shù)超過數(shù)組長度時,會進行數(shù)組擴容。 

  1. public void put(Runnable e) { 
  2.  offer(e); 
  3. }         
  4. public boolean add(Runnable e) {             
  5.     return offer(e); 
  6. }         
  7. public boolean offer(Runnable e, long timeout, TimeUnit unit) {             
  8.     return offer(e); 

 offer添加元素

ScheduledThreadPoolExecutor提交任務(wù)時調(diào)用的是DelayedWorkQueue.add,而add、put等一些對外提供的添加元素的方法都調(diào)用了offer。

  1. public boolean offer(Runnable x) {             
  2.     if (x == null)                 
  3.         throw new NullPointerException(); 
  4.     RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;             
  5.     // 使用lock保證并發(fā)操作安全 
  6.     final ReentrantLock lock = this.lock; 
  7.     lock.lock();             
  8.     try {                 
  9.         int i = size;                 
  10.         // 如果要超過數(shù)組長度,就要進行數(shù)組擴容 
  11.         if (i >= queue.length)                     
  12.             // 數(shù)組擴容 
  13.             grow();                 
  14.         // 將隊列中元素個數(shù)加一 
  15.         size = i + 1;                 
  16.         // 如果是第一個元素,那么就不需要排序,直接賦值就行了 
  17.         if (i == 0) { 
  18.          queue[0] = e; 
  19.             setIndex(e, 0); 
  20.         } else {                     
  21.             // 調(diào)用siftUp方法,使插入的元素變得有序。 
  22.             siftUp(i, e); 
  23.         }                 
  24.         // 表示新插入的元素是隊列頭,更換了隊列頭, 
  25.         // 那么就要喚醒正在等待獲取任務(wù)的線程。 
  26.         if (queue[0] == e) { 
  27.          leader = null;                     
  28.             // 喚醒正在等待等待獲取任務(wù)的線程 
  29.             available.signal(); 
  30.         } 
  31.     } finally { 
  32.      lock.unlock(); 
  33.     }             
  34.     return true

 其基本流程如下:

  1. 其作為生產(chǎn)者的入口,首先獲取鎖。
  2. 判斷隊列是否要滿了(size >= queue.length),滿了就擴容grow()。
  3. 隊列未滿,size+1。
  4. 判斷添加的元素是否是第一個,是則不需要堆化。
  5. 添加的元素不是第一個,則需要堆化siftUp。
  6. 如果堆頂元素剛好是此時被添加的元素,則喚醒take線程消費。
  7. 最終釋放鎖。

offer基本流程圖如下:


擴容grow()

可以看到,當隊列滿時,不會阻塞等待,而是繼續(xù)擴容。新容量newCapacity在舊容量oldCapacity的基礎(chǔ)上擴容50%(oldCapacity >> 1相當于oldCapacity /2)。最后Arrays.copyOf,先根據(jù)newCapacity創(chuàng)建一個新的空數(shù)組,然后將舊數(shù)組的數(shù)據(jù)復制到新數(shù)組中。

  1. private void grow() {             
  2.     int oldCapacity = queue.length;             
  3.     // 每次擴容增加原來數(shù)組的一半數(shù)量。 
  4.     // grow 50% 
  5.     int newCapacity = oldCapacity + (oldCapacity >> 1);  
  6.     if (newCapacity < 0) // overflow 
  7.      newCapacity = Integer.MAX_VALUE;             
  8.     // 使用Arrays.copyOf來復制一個新數(shù)組 
  9.     queue = Arrays.copyOf(queue, newCapacity); 

 向上堆化siftUp

新添加的元素先會加到堆底,然后一步步和上面的父親節(jié)點比較,若小于父親節(jié)點則和父親節(jié)點互換位置,循環(huán)比較直至大于父親節(jié)點才結(jié)束循環(huán)。通過循環(huán),來查找元素key應(yīng)該插入在堆二叉樹那個節(jié)點位置,并交互父節(jié)點的位置。

向上堆化siftUp的詳細過程可以查看《堆和二叉堆的實現(xiàn)和特性》

  1. private void siftUp(int k, RunnableScheduledFuture<?> key) {             
  2.     // 當k==0時,就到了堆二叉樹的根節(jié)點了,跳出循環(huán) 
  3.     while (k > 0) {                 
  4.         // 父節(jié)點位置坐標, 相當于(k - 1) / 2 
  5.         int parent = (k - 1) >>> 1;                 
  6.         // 獲取父節(jié)點位置元素 
  7.         RunnableScheduledFuture<?> e = queue[parent];                 
  8.         // 如果key元素大于父節(jié)點位置元素,滿足條件,那么跳出循環(huán) 
  9.         // 因為是從小到大排序的。 
  10.         if (key.compareTo(e) >= 0)                     
  11.             break;                 
  12.         // 否則就將父節(jié)點元素存放到k位置 
  13.         queue[k] = e;                 
  14.         // 這個只有當元素是ScheduledFutureTask對象實例才有用,用來快速取消任務(wù)。 
  15.         setIndex(e, k);                 
  16.         // 重新賦值k,尋找元素key應(yīng)該插入到堆二叉樹的那個節(jié)點 
  17.         k = parent; 
  18.     }             
  19.     // 循環(huán)結(jié)束,k就是元素key應(yīng)該插入的節(jié)點位置 
  20.     queue[k] = key
  21.     setIndex(key, k); 

 出隊方法

DelayedWorkQueue 提供了以下幾個出隊方法

  • take(),等待獲取隊列頭元素
  • poll() ,立即獲取隊列頭元素
  • poll(long timeout, TimeUnit unit) ,超時等待獲取隊列頭元素

take消費元素

Worker工作線程啟動后就會循環(huán)消費工作隊列中的元素,因為ScheduledThreadPoolExecutor的keepAliveTime=0,所以消費任務(wù)其只調(diào)用了DelayedWorkQueue.take。take基本流程如下:

  • 首先獲取可中斷鎖,判斷堆頂元素是否是空,空的則阻塞等待available.await()。
  • 堆頂元素不為空,則獲取其延遲執(zhí)行時間delay,delay <= 0說明到了執(zhí)行時間,出隊列finishPoll。
  • delay > 0還沒到執(zhí)行時間,判斷l(xiāng)eader線程是否為空,不為空則說明有其他take線程也在等待,當前take將無限期阻塞等待。
  • leader線程為空,當前take線程設(shè)置為leader,并阻塞等待delay時長。
  • 當前l(fā)eader線程等待delay時長自動喚醒或者被其他take線程喚醒,則最終將leader設(shè)置為null。
  • 再循環(huán)一次判斷delay <= 0出隊列。
  • 跳出循環(huán)后判斷l(xiāng)eader為空并且堆頂元素不為空,則喚醒其他take線程,最后是否鎖。
  1. public RunnableScheduledFuture<?> take() throws InterruptedException {             
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lockInterruptibly();             
  4.     try {                 
  5.         for (;;) { 
  6.          RunnableScheduledFuture<?> first = queue[0];                     
  7.             // 如果沒有任務(wù),就讓線程在available條件下等待。 
  8.             if (first == null
  9.              available.await();                     
  10.             else {                         
  11.                 // 獲取任務(wù)的剩余延時時間 
  12.                 long delay = first.getDelay(NANOSECONDS);                         
  13.                 // 如果延時時間到了,就返回這個任務(wù),用來執(zhí)行。 
  14.                 if (delay <= 0)                             
  15.                     return finishPoll(first);                         
  16.                 // 將first設(shè)置為null,當線程等待時,不持有first的引用 
  17.                 first = null; // don't retain ref while waiting 
  18.  
  19.                 // 如果還是原來那個等待隊列頭任務(wù)的線程, 
  20.                 // 說明隊列頭任務(wù)的延時時間還沒有到,繼續(xù)等待。 
  21.                 if (leader != null
  22.                  available.await();                         
  23.                 else {                             
  24.                     // 記錄一下當前等待隊列頭任務(wù)的線程 
  25.                     Thread thisThread = Thread.currentThread(); 
  26.                     leader = thisThread;                             
  27.                     try {                                 
  28.                         // 當任務(wù)的延時時間到了時,能夠自動超時喚醒。 
  29.                         available.awaitNanos(delay); 
  30.                     } finally {                                 
  31.                         if (leader == thisThread) 
  32.                          leader = null
  33.                     } 
  34.                 } 
  35.             } 
  36.         } 
  37.     } finally {                 
  38.         if (leader == null && queue[0] != null)                    // 喚醒等待任務(wù)的線程 
  39.          available.signal(); 
  40.         ock.unlock(); 
  41.     } 

 take基本流程圖如下: 

 

take線程阻塞等待

可以看出這個生產(chǎn)者take線程會在兩種情況下阻塞等待:

  • 堆頂元素為空。
  • 堆頂元素的delay > 0 。

finishPoll出隊列

堆頂元素delay<=0,執(zhí)行時間到,出隊列就是一個向下堆化的過程siftDown。

  1. // 移除隊列頭元素 
  2. private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {             
  3.     // 將隊列中元素個數(shù)減一 
  4.     int s = --size;             
  5.     // 獲取隊列末尾元素x 
  6.     RunnableScheduledFuture<?> x = queue[s];             
  7.     // 原隊列末尾元素設(shè)置為null 
  8.     queue[s] = null;             
  9.     if (s != 0)                 
  10.         // 因為移除了隊列頭元素,所以進行重新排序。 
  11.         siftDown(0, x); 
  12.     setIndex(f, -1);             
  13.     return f; 

堆的刪除方法主要分為三步:

  1. 先將隊列中元素個數(shù)減一;
  2. 將原隊列末尾元素設(shè)置成為隊列頭元素,再將隊列末尾元素設(shè)置為null;
  3. 調(diào)用setDown(O,x)方法,保證按照元素的優(yōu)先級排序。

向下堆化siftDown

由于堆頂元素出隊列后,就破壞了堆的結(jié)構(gòu),需要組織整理下,將堆尾元素移到堆頂,然后向下堆化:

  • 從堆頂開始,父親節(jié)點與左右子節(jié)點中較小的孩子節(jié)點比較(左孩子不一定小于右孩子)。
  • 父親節(jié)點小于等于較小孩子節(jié)點,則結(jié)束循環(huán),不需要交換位置。
  • 若父親節(jié)點大于較小孩子節(jié)點,則交換位置。
  • 繼續(xù)向下循環(huán)判斷父親節(jié)點和孩子節(jié)點的關(guān)系,直到父親節(jié)點小于等于較小孩子節(jié)點才結(jié)束循環(huán)。

向下堆化siftDown的詳細過程可以查看《堆和二叉堆的實現(xiàn)和特性》

  1. private void siftDown(int k, RunnableScheduledFuture<?> key) {      
  2.     // 無符號右移,相當于size/2 
  3.     int half = size >>> 1;             
  4.     // 通過循環(huán),保證父節(jié)點的值不能大于子節(jié)點。 
  5.     while (k < half) {                 
  6.         // 左子節(jié)點, 相當于 (k * 2) + 1 
  7.         int child = (k << 1) + 1;                 
  8.         // 左子節(jié)點位置元素 
  9.         RunnableScheduledFuture<?> c = queue[child];                 
  10.         // 右子節(jié)點, 相當于 (k * 2) + 2 
  11.         int right = child + 1;                 
  12.         // 如果左子節(jié)點元素值大于右子節(jié)點元素值,那么右子節(jié)點才是較小值的子節(jié)點。 
  13.         // 就要將c與child值重新賦值 
  14.         if (right < size && c.compareTo(queue[right]) > 0) 
  15.          c = queue[child = right];                 
  16.         // 如果父節(jié)點元素值小于較小的子節(jié)點元素值,那么就跳出循環(huán) 
  17.         if (key.compareTo(c) <= 0)                     
  18.             break;                 
  19.         // 否則,父節(jié)點元素就要和子節(jié)點進行交換 
  20.         queue[k] = c; 
  21.         setIndex(c, k); 
  22.         k = child; 
  23.     }             
  24.     queue[k] = key
  25.     setIndex(key, k); 

 leader線程

leader線程的設(shè)計,是Leader-Follower模式的變種,旨在于為了不必要的時間等待。當一個take線程變成leader線程時,只需要等待下一次的延遲時間,而不是leader線程的其他take線程則需要等leader線程出隊列了才喚醒其他take線程。

poll()

立即獲取隊列頭元素,當隊列頭任務(wù)是null,或者任務(wù)延時時間沒有到,表示這個任務(wù)還不能返回,因此直接返回null。否則調(diào)用finishPoll方法,移除隊列頭元素并返回。

  1. public RunnableScheduledFuture<?> poll() {             
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lock();             
  4.     try { 
  5.      RunnableScheduledFuture<?> first = queue[0];                 
  6.         // 隊列頭任務(wù)是null,或者任務(wù)延時時間沒有到,都返回null 
  7.         if (first == null || first.getDelay(NANOSECONDS) > 0)                     
  8.             return null;                 
  9.         else 
  10.          // 移除隊列頭元素 
  11.             return finishPoll(first); 
  12.     } finally { 
  13.      lock.unlock(); 
  14.     } 

 poll(long timeout, TimeUnit unit)

超時等待獲取隊列頭元素,與take方法相比較,就要考慮設(shè)置的超時時間,如果超時時間到了,還沒有獲取到有用任務(wù),那么就返回null。其他的與take方法中邏輯一樣。

  1. public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit)             
  2.     throws InterruptedException {             
  3.     long nanos = unit.toNanos(timeout);             
  4.     final ReentrantLock lock = this.lock; 
  5.     lock.lockInterruptibly();             
  6.     try {                 
  7.         for (;;) { 
  8.          RunnableScheduledFuture<?> first = queue[0];                     
  9.             // 如果沒有任務(wù)。 
  10.             if (first == null) {                         
  11.              // 超時時間已到,那么就直接返回null 
  12.                 if (nanos <= 0)                             
  13.                     return null;                         
  14.                 else 
  15.                  // 否則就讓線程在available條件下等待nanos時間 
  16.                     nanos = available.awaitNanos(nanos); 
  17.             } else {                         
  18.                 // 獲取任務(wù)的剩余延時時間 
  19.                 long delay = first.getDelay(NANOSECONDS);                         
  20.                 // 如果延時時間到了,就返回這個任務(wù),用來執(zhí)行。 
  21.                 if (delay <= 0)                             
  22.                     return finishPoll(first);                         
  23.                 // 如果超時時間已到,那么就直接返回null 
  24.                 if (nanos <= 0)                             
  25.                     return null;                         
  26.                 // 將first設(shè)置為null,當線程等待時,不持有first的引用 
  27.                 first = null; // don't retain ref while waiting 
  28.                 // 如果超時時間小于任務(wù)的剩余延時時間,那么就有可能獲取不到任務(wù)。 
  29.                 // 在這里讓線程等待超時時間nanos 
  30.                 if (nanos < delay || leader != null
  31.                  nanos = available.awaitNanos(nanos);                         
  32.                 else { 
  33.                     Thread thisThread = Thread.currentThread(); 
  34.                     leader = thisThread;                             
  35.                     try {                                 
  36.                         // 當任務(wù)的延時時間到了時,能夠自動超時喚醒。 
  37.                         long timeLeft = available.awaitNanos(delay);                                 
  38.                         // 計算剩余的超時時間 
  39.                         nanos -= delay - timeLeft; 
  40.                     } finally {                                 
  41.                         if (leader == thisThread) 
  42.                          leader = null
  43.                     } 
  44.                 } 
  45.             } 
  46.         } 
  47.     } finally {                 
  48.         if (leader == null && queue[0] != null)                    // 喚醒等待任務(wù)的線程 
  49.          available.signal(); 
  50.         lock.unlock(); 
  51.     } 

 remove刪除指定元素

刪除指定元素一般用于取消任務(wù)時,任務(wù)還在阻塞隊列中,則需要將其刪除。當刪除的元素不是堆尾元素時,需要做堆化處理。

  1. public boolean remove(Object x) { 
  2.     final ReentrantLock lock = this.lock; 
  3.     lock.lock(); 
  4.     try { 
  5.         int i = indexOf(x); 
  6.         if (i < 0) 
  7.             return false
  8.         //維護heapIndex 
  9.         setIndex(queue[i], -1); 
  10.         int s = --size; 
  11.         RunnableScheduledFuture<?> replacement = queue[s]; 
  12.         queue[s] = null
  13.         if (s != i) { 
  14.             //刪除的不是堆尾元素,則需要堆化處理 
  15.             //先向下堆化 
  16.             siftDown(i, replacement); 
  17.             if (queue[i] == replacement) 
  18.                 //若向下堆化后,i位置的元素還是replacement,說明四無需向下堆化的, 
  19.                 //則需要向上堆化 
  20.                 siftUp(i, replacement); 
  21.         } 
  22.         return true
  23.     } finally { 
  24.         lock.unlock(); 
  25.     } 

 總結(jié)

使用優(yōu)先級隊列DelayedWorkQueue,保證添加到隊列中的任務(wù),會按照任務(wù)的延時時間進行排序,延時時間少的任務(wù)首先被獲取。

  1. DelayedWorkQueue的數(shù)據(jù)結(jié)構(gòu)是基于堆實現(xiàn)的;
  2. DelayedWorkQueue采用數(shù)組實現(xiàn)堆,根節(jié)點出隊,用最后葉子節(jié)點替換,然后下推至滿足堆成立條件;最后葉子節(jié)點入隊,然后向上推至滿足堆成立條件;
  3. DelayedWorkQueue添加元素滿了之后會自動擴容原來容量的1/2,即永遠不會阻塞,最大擴容可達Integer.MAX_VALUE,所以線程池中至多有corePoolSize個工作線程正在運行;
  4. DelayedWorkQueue 消費元素take,在堆頂元素為空和delay >0 時,阻塞等待;
  5. DelayedWorkQueue 是一個生產(chǎn)永遠不會阻塞,消費可以阻塞的生產(chǎn)者消費者模式;
  6. DelayedWorkQueue 有一個leader線程的變量,是Leader-Follower模式的變種。當一個take線程變成leader線程時,只需要等待下一次的延遲時間,而不是leader線程的其他take線程則需要等leader線程出隊列了才喚醒其他take線程。 

 

責任編輯:姜華 來源: 今日頭條
相關(guān)推薦

2020-11-19 07:41:51

ArrayBlocki

2020-11-24 09:04:55

PriorityBlo

2020-11-20 06:22:02

LinkedBlock

2017-04-12 10:02:21

Java阻塞隊列原理分析

2025-01-14 00:00:00

Blocking隊列元素

2012-06-14 10:34:40

Java阻塞搜索實例

2023-12-28 07:49:11

線程池源碼應(yīng)用場景

2023-12-15 09:45:21

阻塞接口

2025-04-02 01:20:00

阻塞隊列源碼

2021-06-04 14:15:10

鴻蒙HarmonyOS應(yīng)用

2022-06-30 08:14:05

Java阻塞隊列

2024-10-14 12:34:08

2024-02-20 08:16:10

阻塞隊列源碼

2021-09-22 14:36:32

鴻蒙HarmonyOS應(yīng)用

2014-08-26 11:11:57

AsyncHttpCl源碼分析

2011-03-15 11:33:18

iptables

2023-12-05 13:46:09

解密協(xié)程線程隊列

2011-05-26 10:05:48

MongoDB

2021-05-12 09:45:20

鴻蒙HarmonyOS應(yīng)用

2022-06-30 14:31:57

Java阻塞隊列
點贊
收藏

51CTO技術(shù)棧公眾號