java并發編程藝術

      網友投稿 650 2025-04-04

      參考博客 :https://blog.csdn.net/LQL_King/article/details/77146647

      Volatile

      理解volatile特性的一個好方法是把對volatile變量的單個讀/寫,看成是使用同一個鎖對這些單個讀/寫操作做了同步。下面通過具體的示例來說明,示例代碼如下。

      class?VolatileFeaturesExample?{ volatile?long?vl?=?0L;?//?使用volatile聲明64位的long型變量 public?void?set(long?l)?{ vl?=?l;?//?單個volatile變量的寫 } public?void?getAndIncrement?()?{ vl++;?//?復合(多個)volatile變量的讀/寫 } public?long?get()?{ return?vl;?//?單個volatile變量的讀 } }

      假設有多個線程分別調用上面程序的3個方法,這個程序在語義上和下面程序等價。

      class?VolatileFeaturesExample?{ long?vl?=?0L;?//?64位的long型普通變量 public?synchronized?void?set(long?l)?{?//?對單個的普通變量的寫用同一個鎖同步 vl?=?l; } public?void?getAndIncrement?()?{?//?普通方法調用 long?temp?=?get();?//?調用已同步的讀方法 temp?+=?1L;?//?普通寫操作 set(temp);?//?調用已同步的寫方法 } public?synchronized?long?get()?{?//?對單個的普通變量的讀用同一個鎖同步 return?vl; } }

      volatile變量自身具有下列特性。

      ·可見性。對一個volatile變量的讀,總是能看到(任意線程)對這個volatile變量最后的寫

      入。

      ·原子性:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不

      具有原子性。

      對于普通同步方法,鎖是當前實例對象。

      ·對于靜態同步方法,鎖是當前類的Class對象。

      ·對于同步方法塊,鎖是Synchonized括號里配置的對象。

      當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖

      Java支持多個線程同時訪問一個對象或者對象的成員變量,由于每個線程可以擁有這個變量的拷貝(雖然對象以及成員變量分配的內存是在共享內存中的,但是每個執行的線程還是可以擁有一份拷貝,這樣做的目的是加速程序的執行,這是現代多核處理器的一個顯著特性),所以程序在執行過程中,一個線程看到的變量并不一定是最新的。關鍵字volatile可以用來修飾字段(成員變量),就是告知程序任何對該變量的訪問均需要從共享內存中獲取,而對它的改變必須同步刷新回共享內存,它能保證所有線程對變量訪問的可見性。

      舉個例子,定義一個表示程序是否運行的成員變量boolean on=true,那么另一個線程可能對它執行關閉動作(on=false),這里涉及多個線程對變量的訪問,因此需要將其定義成為volatile boolean on=true,這樣其他線程對它進行改變時,可以讓所有線程感知到變化,因為所有對on變量的訪問和修改都需要以共享內存為準。但是,過多地使用volatile是不必要的,因為它會降低程序執行的效率。關鍵字synchronized可以修飾方法或者以同步塊的形式來進行使用,它主要確保多個線程在同一個時刻,只能有一個線程處于方法或者同步塊中,它保證了線程對變量訪問的可見性和排他性。

      wait()? /? notify()

      調用wait()、notify()以及notifyAll()時需要注意的細節,如下。

      1)使用wait()、notify()和notifyAll()時需要先對調用對象加鎖。

      2)調用wait()方法后,線程狀態由RUNNING變為WAITING,并將當前線程放置到對象的等待隊列。

      3)notify()或notifyAll()方法調用后,等待線程依舊不會從wait()返回,需要調用notify()或notifAll()的線程釋放鎖之后,等待線程才有機會從wait()返回。

      4)notify()方法將等待隊列中的一個等待線程從等待隊列中移到同步隊列中,而notifyAll()方法則是將等待隊列中所有的線程全部移到同步隊列,被移動的線程狀態由WAITING變為BLOCKED。

      5)從wait()方法返回的前提是獲得了調用對象的鎖。

      管道輸入/輸出流

      管道輸入/輸出流和普通的文件輸入/輸出流或者網絡輸入/輸出流不同之處在于,它主要用于線程之間的數據傳輸,而傳輸的媒介為內存。

      管道輸入/輸出流主要包括了如下4種具體實現:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前兩種面向字節,而后兩種面向字符。

      在代碼清單4-12所示的例子中,創建了printThread,它用來接受main線程的輸入,任何main線程的輸入均通過PipedWriter寫入,而printThread在另一端通過PipedReader將內容讀出并打印。

      public?class?Piped?{ ?public?static?void?main(String[]?args)?throws?Exception?{ ??PipedWriter?out?=?new?PipedWriter(); ??PipedReader?in?=?new?PipedReader(); ??//?將輸出流和輸入流進行連接,否則在使用時會拋出IOException ??out.connect(in); ?? ??Thread?printThread?=?new?Thread(new?Print(in),?"PrintThread"); ??printThread.start(); ??int?receive?=?0; ??try?{ ???while?((receive?=?System.in.read())?!=?-1)?{ ????out.write(receive); ???} ??}?finally?{ ???out.close(); ??} ?} ?static?class?Print?implements?Runnable?{ ??private?PipedReader?in; ??public?Print(PipedReader?in)?{ ???this.in?=?in; ??} ??public?void?run()?{ ???int?receive?=?0; ???try?{ ????while?((receive?=?in.read())?!=?-1)?{ ?????System.out.print((char)?receive); ????} ???}?catch?(IOException?ex)?{ ???} ??} ?} }

      對于Piped類型的流,必須先要進行綁定,也就是調用connect()方法,如果沒有將輸入/輸出流綁定起來,對于該流的訪問將會拋出異常。

      LOCK(重入鎖與讀寫鎖)

      Lock?lock?=?new?ReentrantLock();?//重入鎖(可多次上鎖) lock.lock(); try?{ }?finally?{ lock.unlock(); }

      在finally塊中釋放鎖,目的是保證在獲取到鎖之后,最終能夠被釋放。

      不要將獲取鎖的過程寫在try塊中,因為如果在獲取鎖(自定義鎖的實現)時發生了異常,異常拋出的同時,也會導致鎖無故釋放。

      表5-1 Lock接口提供的synchronized關鍵字不具備的主要特性

      之前提到鎖(如Mutex和ReentrantLock)基本都是排他鎖,這些鎖在同一時刻只允許一個線程進行訪問,而讀寫鎖在同一時刻可以允許多個讀線程訪問,但是在寫線程訪問時,所有的讀線程和其他寫線程均被阻塞。讀寫鎖維護了一對鎖,一個讀鎖和一個寫鎖,通過分離讀鎖和寫鎖,使得并發性相比一般的排他鎖有了很大提升。

      除了保證寫操作對讀操作的可見性以及并發性的提升之外,讀寫鎖能夠簡化讀寫交互場景的編程方式。假設在程序中定義一個共享的用作緩存數據結構,它大部分時間提供讀服務(例如查詢和搜索),而寫操作占有的時間很少,但是寫操作完成之后的更新需要對后續的讀服務可見。

      在沒有讀寫鎖支持的(Java 5之前)時候,如果需要完成上述工作就要使用Java的等待通知機制,就是當寫操作開始時,所有晚于寫操作的讀操作均會進入等待狀態,只有寫操作完成并進行通知之后,所有等待的讀操作才能繼續執行(寫操作之間依靠synchronized關鍵進行同步),這樣做的目的是使讀操作能讀取到正確的數據,不會出現臟讀。改用讀寫鎖實現上述功

      能,只需要在讀操作時獲取讀鎖,寫操作時獲取寫鎖即可。當寫鎖被獲取到時,后續(非當前寫操作線程)的讀寫操作都會被阻塞,寫鎖釋放之后,所有操作繼續執行,編程方式相對于使用等待通知機制的實現方式而言,變得簡單明了。

      一般情況下,讀寫鎖的性能都會比排它鎖好,因為大多數場景讀是多于寫的。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發性和吞吐量。Java并發包提供讀寫鎖的實現是ReentrantReadWriteLock,它提供的特性如表5-8所示。

      當需要阻塞或喚醒一個線程的時候,都會使用LockSupport工具類來完成相應工作。LockSupport定義了一組的公共靜態方法,這些方法提供了最基本的線程阻塞和喚醒功能,而LockSupport也成為構建同步組件的基礎工具。

      LockSupport定義了一組以park開頭的方法用來阻塞當前線程,以及unpark(Thread thread)方法來喚醒一個被阻塞的線程。Park有停車的意思,假設線程為車輛,那么park方法代表著停車,而unpark方法則是指車輛啟動離開,這些方法以及描述如表5-10所示。

      表5-10 LockSupport提供的阻塞和喚醒方法

      在Java 6中,LockSupport增加了park(Object blocker)、parkNanos(Object blocker,long nanos)和parkUntil(Object blocker,long deadline)3個方法,用于實現阻塞當前線程的功能,其中參數blocker是用來標識當前線程在等待的對象(以下稱為阻塞對象),該對象主要用于問題排查和系統監控。

      下面的示例中,將對比parkNanos(long nanos)方法和parkNanos(Object blocker,long nanos)方法來展示阻塞對象blocker的用處,代碼片段和線程dump(部分)如表5-11所示。

      從表5-11的線程dump結果可以看出,代碼片段的內容都是阻塞當前線程10秒,但從線程dump結果可以看出,有阻塞對象的parkNanos方法能夠傳遞給開發人員更多的現場信息。這是由于在Java 5之前,當線程阻塞(使用synchronized關鍵字)在一個對象上時,通過線程dump能夠查看到該線程的阻塞對象,方便問題定位,而Java 5推出的Lock等并發工具時卻遺漏了這一

      點,致使在線程dump時無法提供阻塞對象的信息。因此,在Java 6中,LockSupport新增了上述3個含有阻塞對象的park方法,用以替代原有的park方法。

      Condition

      public?class?BoundedQueue?{ private?Object[]?items; //?添加的下標,刪除的下標和數組當前數量 private?int?addIndex,?removeIndex,?count; private?Lock?lock?=?new?ReentrantLock(); private?Condition?notEmpty?=?lock.newCondition(); private?Condition?notFull?=?lock.newCondition(); public?BoundedQueue(int?size)?{ items?=?new?Object[size]; } //?添加一個元素,如果數組滿,則添加線程進入等待狀態,直到有"空位" public?void?add(T?t)?throws?InterruptedException?{ lock.lock(); ?try?{ while?(count?==?items.length) notFull.await(); items[addIndex]?=?t; if?(++addIndex?==?items.length) addIndex?=?0; ++count; notEmpty.signal(); }?finally?{ lock.unlock(); } } //?由頭部刪除一個元素,如果數組空,則刪除線程進入等待狀態,直到有新添加元素 @SuppressWarnings("unchecked") public?T?remove()?throws?InterruptedException?{ lock.lock(); try?{ while?(count?==?0) notEmpty.await(); Object?x?=?items[removeIndex]; if?(++removeIndex?==?items.length) removeIndex?=?0; --count; notFull.signal(); return?(T)?x; }?finally?{ lock.unlock(); } } }

      上述示例中,BoundedQueue通過add(T t)方法添加一個元素,通過remove()方法移出一個元素。以添加方法為例。

      首先需要獲得鎖,目的是確保數組修改的可見性和排他性。當數組數量等于數組長度時,表示數組已滿,則調用notFull.await(),當前線程隨之釋放鎖并進入等待狀態。如果數組數量不等于數組長度,表示數組未滿,則添加元素到數組中,同時通知等待在notEmpty上的線程,數組中已經有新元素可以獲取。在添加和刪除方法中使用while循環而非if判斷,目的是防止過早或意外的通知,只有條件符合才能夠退出循環?;叵胫疤岬降牡却?通知的經典范式,二者是非常類似的。

      Java里的阻塞隊列

      JDK 7提供了7個阻塞隊列,如下。

      ·ArrayBlockingQueue:一個由數組結構組成的有界阻塞隊列。

      ·LinkedBlockingQueue:一個由鏈表結構組成的有界阻塞隊列。

      ·PriorityBlockingQueue:一個支持優先級排序的無界阻塞隊列。

      ·DelayQueue:一個使用優先級隊列實現的無界阻塞隊列。

      ·SynchronousQueue:一個不存儲元素的阻塞隊列。

      ·LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列。

      ·LinkedBlockingDeque:一個由鏈表結構組成的雙向阻塞隊列。

      1.ArrayBlockingQueue

      ArrayBlockingQueue是一個用數組實現的有界阻塞隊列。此隊列按照先進先出(FIFO)的原則對元素進行排序。

      默認情況下不保證線程公平的訪問隊列,所謂公平訪問隊列是指阻塞的線程,可以按照阻塞的先后順序訪問隊列,即先阻塞線程先訪問隊列。非公平性是對先等待的線程是非公平的,當隊列可用時,阻塞的線程都可以爭奪訪問隊列的資格,有可能先阻塞的線程最后才訪問隊列。為了保證公平性,通常會降低吞吐量。我們可以使用以下代碼創建一個公平的阻塞隊列。

      ArrayBlockingQueue?fairQueue?=?new?ArrayBlockingQueue(1000,true); 訪問者的公平性是使用可重入鎖實現的,代碼如下。 public?ArrayBlockingQueue(int?capacity,?boolean?fair)?{ if?(capacity?<=?0) throw?new?IllegalArgumentException(); this.items?=?new?Object[capacity]; lock?=?new?ReentrantLock(fair); notEmpty?=?lock.newCondition(); notFull?=?lock.newCondition(); }

      2.LinkedBlockingQueue

      LinkedBlockingQueue是一個用鏈表實現的有界阻塞隊列。此隊列的默認和最大長度為Integer.MAX_VALUE。此隊列按照先進先出的原則對元素進行排序。

      3.PriorityBlockingQueue

      PriorityBlockingQueue是一個支持優先級的無界阻塞隊列。默認情況下元素采取自然順序升序排列。也可以自定義類實現compareTo()方法來指定元素排序規則,或者初始化PriorityBlockingQueue時,指定構造參數Comparator來對元素進行排序。需要注意的是不能保證同優先級元素的順序。

      4.DelayQueue

      DelayQueue是一個支持延時獲取元素的無界阻塞隊列。隊列使用PriorityQueue來實現。隊列中的元素必須實現Delayed接口,在創建元素時可以指定多久才能從隊列中獲取當前元素。只有在延遲期滿時才能從隊列中提取元素。

      DelayQueue非常有用,可以將DelayQueue運用在以下應用場景。

      ·緩存系統的設計:可以用DelayQueue保存緩存元素的有效期,使用一個線程循環查詢

      DelayQueue,一旦能從DelayQueue中獲取元素時,表示緩存有效期到了。

      ·定時任務調度:使用DelayQueue保存當天將會執行的任務和執行時間,一旦從

      DelayQueue中獲取到任務就開始執行,比如TimerQueue就是使用DelayQueue實現的。

      (1)如何實現Delayed接口

      DelayQueue隊列的元素必須實現Delayed接口。我們可以參考ScheduledThreadPoolExecutor

      里ScheduledFutureTask類的實現,一共有三步。

      第一步:在對象創建的時候,初始化基本數據。使用time記錄當前對象延遲到什么時候可

      以使用,使用sequenceNumber來標識元素在隊列中的先后順序。代碼如下。

      private?static?final?AtomicLong?sequencer?=?new?AtomicLong(0); ScheduledFutureTask(Runnable?r,?V?result,?long?ns,?long?period)?{ ScheduledFutureTask(Runnable?r,?V?result,?long?ns,?long?period)?{ super(r,?result); this.time?=?ns; this.period?=?period; this.sequenceNumber?=?sequencer.getAndIncrement(); }

      第二步:實現getDelay方法,該方法返回當前元素還需要延時多長時間,單位是納秒,代碼

      如下。

      public?long?getDelay(TimeUnit?unit)?{ return?unit.convert(time?-?now(),?TimeUnit.NANOSECONDS); }

      通過構造函數可以看出延遲時間參數ns的單位是納秒,自己設計的時候最好使用納秒,因

      為實現getDelay()方法時可以指定任意單位,一旦以秒或分作為單位,而延時時間又精確不到

      納秒就麻煩了。使用時請注意當time小于當前時間時,getDelay會返回負數。

      第三步:實現compareTo方法來指定元素的順序。例如,讓延時時間最長的放在隊列的末

      java并發編程的藝術

      尾。實現代碼如下。

      public?int?compareTo(Delayed?other)?{ if?(other?==?this)?//?compare?zero?ONLY?if?same?object return?0; if?(other?instanceof?ScheduledFutureTask)?{ ScheduledFutureTask<>?x?=?(ScheduledFutureTask<>)other; long?diff?=?time?-?x.time; if?(diff??0) return?1; else?if?(sequenceNumber?

      (2)如何實現延時阻塞隊列

      延時阻塞隊列的實現很簡單,當消費者從隊列里獲取元素時,如果元素沒有達到延時時

      間,就阻塞當前線程。

      public?int?compareTo(Delayed?other)?{ if?(other?==?this)?//?compare?zero?ONLY?if?same?object return?0; if?(other?instanceof?ScheduledFutureTask)?{ ScheduledFutureTask<>?x?=?(ScheduledFutureTask<>)other; long?diff?=?time?-?x.time; if?(diff??0) return?1; else?if?(sequenceNumber?

      代碼中的變量leader是一個等待獲取隊列頭部元素的線程。如果leader不等于空,表示已

      經有線程在等待獲取隊列的頭元素。所以,使用await()方法讓當前線程等待信號。如果leader

      等于空,則把當前線程設置成leader,并使用awaitNanos()方法讓當前線程等待接收信號或等

      待delay時間。

      5.SynchronousQueue

      SynchronousQueue是一個不存儲元素的阻塞隊列。每一個put操作必須等待一個take操作,

      否則不能繼續添加元素。

      它支持公平訪問隊列。默認情況下線程采用非公平性策略訪問隊列。使用以下構造方法

      可以創建公平性訪問的SynchronousQueue,如果設置為true,則等待的線程會采用先進先出的

      順序訪問隊列。

      public?SynchronousQueue(boolean?fair)?{ transferer?=?fair?new?TransferQueue()?:?new?TransferStack(); }

      SynchronousQueue可以看成是一個傳球手,負責把生產者線程處理的數據直接傳遞給消費

      者線程。隊列本身并不存儲任何元素,非常適合傳遞性場景。SynchronousQueue的吞吐量高于

      LinkedBlockingQueue和ArrayBlockingQueue。

      6.LinkedTransferQueue

      LinkedTransferQueue是一個由鏈表結構組成的無界阻塞TransferQueue隊列。相對于其他阻

      塞隊列,LinkedTransferQueue多了tryTransfer和transfer方法。

      (1)transfer方法

      如果當前有消費者正在等待接收元素(消費者使用take()方法或帶時間限制的poll()方法

      時),transfer方法可以把生產者傳入的元素立刻transfer(傳輸)給消費者。如果沒有消費者在等

      待接收元素,transfer方法會將元素存放在隊列的tail節點,并等到該元素被消費者消費了才返

      回。transfer方法的關鍵代碼如下。

      Node pred = tryAppend(s, haveData);

      return awaitMatch(s, pred, e, (how == TIMED), nanos);

      第一行代碼是試圖把存放當前元素的s節點作為tail節點。第二行代碼是讓CPU自旋等待

      消費者消費元素。因為自旋會消耗CPU,所以自旋一定的次數后使用Thread.yield()方法來暫停

      當前正在執行的線程,并執行其他線程。

      (2)tryTransfer方法

      tryTransfer方法是用來試探生產者傳入的元素是否能直接傳給消費者。如果沒有消費者等

      待接收元素,則返回false。和transfer方法的區別是tryTransfer方法無論消費者是否接收,方法

      立即返回,而transfer方法是必須等到消費者消費了才返回。

      對于帶有時間限制的tryTransfer(E e,long timeout,TimeUnit unit)方法,試圖把生產者傳入

      的元素直接傳給消費者,但是如果沒有消費者消費該元素則等待指定的時間再返回,如果超

      時還沒消費元素,則返回false,如果在超時時間內消費了元素,則返回true。

      7.LinkedBlockingDeque

      LinkedBlockingDeque是一個由鏈表結構組成的雙向阻塞隊列。所謂雙向隊列指的是可以

      從隊列的兩端插入和移出元素。雙向隊列因為多了一個操作隊列的入口,在多線程同時入隊

      時,也就減少了一半的競爭。相比其他的阻塞隊列,LinkedBlockingDeque多了addFirst、

      addLast、offerFirst、offerLast、peekFirst和peekLast等方法,以First單詞結尾的方法,表示插入、

      獲?。╬eek)或移除雙端隊列的第一個元素。以Last單詞結尾的方法,表示插入、獲取或移除雙

      端隊列的最后一個元素。另外,插入方法add等同于addLast,移除方法remove等效于

      removeFirst。但是take方法卻等同于takeFirst,不知道是不是JDK的bug,使用時還是用帶有First

      和Last后綴的方法更清楚。

      在初始化LinkedBlockingDeque時可以設置容量防止其過度膨脹。另外,雙向阻塞隊列可以

      運用在“工作竊取”模式中。

      線程池

      java中的線程池是運用場景最多的并發框架,幾乎所有需要異步或并發執行任務的程序都可以使用線程池。在開發過程中,合理地使用線程池能夠帶來3個好處。

      第一:降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的消耗。

      第二:提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。

      第三:提高線程的可管理性。線程是稀缺資源,如果無限制地創建,不僅會消耗系統資源,

      還會降低系統的穩定性,使用線程池可以進行統一分配、調優和監控。但是,要做到合理利用線程池,必須對其實現原理了如指掌。

      ThreadPoolExecutor執行execute方法分下面4種情況。

      1)如果當前運行的線程少于corePoolSize,則創建新線程來執行任務(注意,執行這一步驟需要獲取全局鎖)。

      2)如果運行的線程等于或多于corePoolSize,則將任務加入BlockingQueue。

      3)如果無法將任務加入BlockingQueue(隊列已滿),則創建新的線程來處理任務(注意,執行這一步驟需要獲取全局鎖)。

      4)如果創建新線程將使當前運行的線程超出maximumPoolSize,任務將被拒絕,并調用

      RejectedExecutionHandler.rejectedExecution()方法。

      想合理地配置線程池,就必須首先分析任務特性,可以從以下幾個角度來分析。

      ·任務的性質:CPU密集型任務、IO密集型任務和混合型任務。

      ·任務的優先級:高、中和低。

      ·任務的執行時間:長、中和短。

      ·任務的依賴性:是否依賴其他系統資源,如數據庫連接。

      性質不同的任務可以用不同規模的線程池分開處理。CPU密集型任務應配置盡可能小的線程,如配置N cpu +1個線程的線程池。由于IO密集型任務線程并不是一直在執行任務,則應配置盡可能多的線程,如2*N cpu ?;旌闲偷娜蝿?,如果可以拆分,將其拆分成一個CPU密集型任務和一個IO密集型任務,只要這兩個任務執行的時間相差不是太大,那么分解后執行的吞吐量將高于串行執行的吞吐量。如果這兩個任務執行時間相差太大,則沒必要進行分解??梢酝ㄟ^Runtime.getRuntime().availableProcessors()方法獲得當前設備的CPU個數。

      建議使用有界隊列。有界隊列能增加系統的穩定性和預警能力,可以根據需要設大一點兒,比如幾千。有一次,我們系統里后臺任務線程池的隊列和線程池全滿了,不斷拋出拋棄任務的異常,通過排查發現是數據庫出現了問題,導致執行SQL變得非常緩慢,因為后臺任務線程池里的任務全是需要向數據庫查詢和插入數據的,所以導致線程池里的工作線程全部阻塞,任務積壓在線程池里。如果當時我們設置成無界隊列,那么線程池的隊列就會越來越多,有可能會撐滿內存,導致整個系統不可用,而不只是后臺任務出現問題。當然,我們的系統所有的任務是用單獨的服務器部署的,我們使用不同規模的線程池完成不同類型的任務,但是出現這樣問題時也會影響到其他任務.

      可以通過調用線程池的shutdown或shutdownNow方法來關閉線程池。它們的原理是遍歷線程池中的工作線程,然后逐個調用線程的interrupt方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止。但是它們存在一定的區別,shutdownNow首先將線程池的狀態設置成STOP,然后嘗試停止所有的正在執行或暫停任務的線程,并返回等待執行任務的列表,而shutdown只是將線程池的狀態設置成SHUTDOWN狀態,然后中斷所有沒有正在執行任務的線程。

      只要調用了這兩個關閉方法中的任意一個,isShutdown方法就會返回true。當所有的任務都已關閉后,才表示線程池關閉成功,這時調用isTerminaed方法會返回true。至于應該調用哪一種方法來關閉線程池,應該由提交到線程池的任務特性決定,通常調用shutdown方法來關閉線程池,如果任務不一定要執行完,則可以調用shutdownNow方法。

      如果在系統中大量使用線程池,則有必要對線程池進行監控,方便在出現問題時,可以根據線程池的使用狀況快速定位問題??梢酝ㄟ^線程池提供的參數進行監控,在監控線程池的時候可以使用以下屬性。

      ·taskCount:線程池需要執行的任務數量。

      ·completedTaskCount:線程池在運行過程中已完成的任務數量,小于或等于taskCount。

      ·largestPoolSize:線程池里曾經創建過的最大線程數量。通過這個數據可以知道線程池是

      否曾經滿過。如該數值等于線程池的最大大小,則表示線程池曾經滿過。

      ·getPoolSize:線程池的線程數量。如果線程池不銷毀的話,線程池里的線程不會自動銷

      毀,所以這個大小只增不減。

      ·getActiveCount:獲取活動的線程數。

      多線程 Java

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:wps演示文檔在不顯示縮略圖,每次找的時候都要一個一個找,文檔設置默認是勾選了顯示預覽圖的
      下一篇:Excel中進行表格繁體字轉換為簡體字的操作技巧(excel表格繁體文字怎么轉換為簡體)
      相關文章
      亚洲白色白色永久观看| 亚洲欧洲av综合色无码| 亚洲深深色噜噜狠狠网站| 精品无码一区二区三区亚洲桃色| 亚洲AV午夜成人片| 色久悠悠婷婷综合在线亚洲| 亚洲国产成人久久精品99| 亚洲国产天堂久久久久久| 亚洲日韩VA无码中文字幕| 亚洲色欲久久久久综合网| 国外亚洲成AV人片在线观看| 狠狠色婷婷狠狠狠亚洲综合 | 亚洲国产成人久久77| 亚洲精品成人久久| 亚洲人成网站在线观看播放青青 | 亚洲国产精品狼友中文久久久 | 亚洲日本精品一区二区| 亚洲人成在线播放网站岛国| 久久精品国产亚洲AV无码麻豆| 亚洲午夜视频在线观看| 亚洲精品午夜在线观看| 亚洲国产人成在线观看| 亚洲sss综合天堂久久久| 亚洲六月丁香六月婷婷蜜芽 | 亚洲人成影院在线无码按摩店| 在线观看亚洲精品福利片| 国产精品亚洲片在线观看不卡 | 亚洲欧美在线x视频| 亚洲国产专区一区| 久久久久国产亚洲AV麻豆| 亚洲热线99精品视频| 久久精品国产亚洲AV麻豆不卡| 亚洲色大成网站WWW久久九九 | 亚洲最大AV网站在线观看| 最新亚洲人成网站在线观看| 欧洲亚洲综合一区二区三区| 亚洲精品无码你懂的网站| 亚洲人成色77777| 亚洲最新视频在线观看| 亚洲第一成年人网站| 亚洲色丰满少妇高潮18p|