kxbin
          成功留給有準備的人
          posts - 10,  comments - 35,  trackbacks - 0

          很多開發者談到Java多線程開發,僅僅停留在new Thread(...).start()或直接使用Executor框架這個層面,對于線程的管理和控制卻不夠深入,通過讀《Java并發編程實踐》了解到了很多不為我知但又非常重要的細節,今日整理如下。

          不應用線程池的缺點

          有些開發者圖省事,遇到需要多線程處理的地方,直接new Thread(...).start(),對于一般場景是沒問題的,但如果是在并發請求很高的情況下,就會有些隱患:

          • 新建線程的開銷。線程雖然比進程要輕量許多,但對于JVM來說,新建一個線程的代價還是挺大的,決不同于新建一個對象
          • 資源消耗量。沒有一個池來限制線程的數量,會導致線程的數量直接取決于應用的并發量,這樣有潛在的線程數據巨大的可能,那么資源消耗量將是巨大的
          • 穩定性。當線程數量超過系統資源所能承受的程度,穩定性就會成問題

          制定執行策略

          在每個需要多線程處理的地方,不管并發量有多大,需要考慮線程的執行策略

          • 任務以什么順序執行
          • 可以有多少個任何并發執行
          • 可以有多少個任務進入等待執行隊列
          • 系統過載的時候,應該放棄哪些任務?如何通知到應用程序?
          • 一個任務的執行前后應該做什么處理

          線程池的類型

          不管是通過Executors創建線程池,還是通過Spring來管理,都得清楚知道有哪幾種線程池:

          • FixedThreadPool:定長線程池,提交任務時創建線程,直到池的最大容量,如果有線程非預期結束,會補充新線程
          • CachedThreadPool:可變線程池,它猶如一個彈簧,如果沒有任務需求時,它回收空閑線程,如果需求增加,則按需增加線程,不對池的大小做限制
          • SingleThreadExecutor:單線程。處理不過來的任務會進入FIFO隊列等待執行
          • SecheduledThreadPool:周期性線程池。支持執行周期性線程任務

          其實,這些不同類型的線程池都是通過構建一個ThreadPoolExecutor來完成的,所不同的是corePoolSize,maximumPoolSize,keepAliveTime,unit,workQueue,threadFactory這么幾個參數。具體可以參見JDK DOC。

          線程池飽和策略

          由以上線程池類型可知,除了CachedThreadPool其他線程池都有飽和的可能,當飽和以后就需要相應的策略處理請求線程的任務,ThreadPoolExecutor采取的方式通過隊列來存儲這些任務,當然會根據池類型不同選擇不同的隊列,比如FixedThreadPool和SingleThreadExecutor默認采用的是無限長度的LinkedBlockingQueue。但從系統可控性講,最好的做法是使用定長的ArrayBlockingQueue或有限的LinkedBlockingQueue,并且當達到上限時通過ThreadPoolExecutor.setRejectedExecutionHandler方法設置一個拒絕任務的策略,JDK提供了AbortPolicy、CallerRunsPolicy、DiscardPolicy、DiscardOldestPolicy幾種策略,具體差異可見JDK DOC

          線程無依賴性

          多線程任務設計上盡量使得各任務是獨立無依賴的,所謂依賴性可兩個方面:

          • 線程之間的依賴性。如果線程有依賴可能會造成死鎖或饑餓
          • 調用者與線程的依賴性。調用者得監視線程的完成情況,影響可并發量

          當然,在有些業務里確實需要一定的依賴性,比如調用者需要得到線程完成后結果,傳統的Thread是不便完成的,因為run方法無返回值,只能通過一些共享的變量來傳遞結果,但在Executor框架里可以通過Future和Callable實現需要有返回值的任務,當然線程的異步性導致需要有相應機制來保證調用者能等待任務完成,關于Future和Callable的用法見下面的實例就一目了然了:

           

          1. public class FutureRenderer {  
          2.     private final ExecutorService executor = ...;  
          3.     void renderPage(CharSequence source) {  
          4.         final List<ImageInfo> imageInfos = scanForImageInfo(source);  
          5.         Callable<List<ImageData>> task =  
          6.                 new Callable<List<ImageData>>() {  
          7.                     public List<ImageData> call() {  
          8.                         List<ImageData> result  
          9.                                 = new ArrayList<ImageData>();  
          10.                         for (ImageInfo imageInfo : imageInfos)  
          11.                             result.add(imageInfo.downloadImage());  
          12.                         return result;  
          13.                     }  
          14.                 };  
          15.         Future<List<ImageData>> future =  executor.submit(task);  
          16.         renderText(source);  
          17.         try {  
          18.             List<ImageData> imageData =  future.get();  
          19.             for (ImageData data : imageData)  
          20.                 renderImage(data);  
          21.         } catch (InterruptedException e) {  
          22.             // Re-assert the thread's interrupted status  
          23.             Thread.currentThread().interrupt();  
          24.             // We don't need the result, so cancel the task too  
          25.             future.cancel(true);  
          26.         } catch (ExecutionException e) {  
          27.             throw launderThrowable(e.getCause());  
          28.         }  
          29.     }  
          30. }  

           

          以上代碼關鍵在于List<ImageData> imageData = future.get();如果Callable類型的任務沒有執行完時,調用者會阻塞等待。不過這樣的方式還是得謹慎使用,很容易造成不良設計。另外對于這種需要等待的場景,就需要設置一個最大容忍時間timeout,設置方法可以在future.get()加上timeout參數,或是再調用ExecutorService.invokeAll 加上timeout參數

          線程的取消與關閉 

          一般的情況下是讓線程運行完成后自行關閉,但有些時候也會中途取消或關閉線程,比如以下情況:

          • 調用者強制取消。比如一個長時間運行的任務,用戶點擊"cancel"按鈕強行取消
          • 限時任務
          • 發生不可處理的任務
          • 整個應用程序或服務的關閉

          因此需要有相應的取消或關閉的方法和策略來控制線程,一般有以下方法:

          1)通過變量標識來控制

          這種方式比較老土,但使用得非常廣泛,主要缺點是對有阻塞的操作控制不好,代碼示例如下所示:

           

          1. public class PrimeGenerator implements Runnable {  
          2.      @GuardedBy("this")  
          3.      private final List<BigInteger> primes  
          4.              = new ArrayList<BigInteger>();  
          5.      private  volatile boolean cancelled;  
          6.      public void run() {  
          7.          BigInteger p = BigInteger.ONE;  
          8.          while (!cancelled ) {  
          9.              p = p.nextProbablePrime();  
          10.              synchronized (this) {  
          11.                  primes.add(p);  
          12.              }  
          13.          }  
          14.      }  
          15.      public void cancel() { cancelled = true;  }  
          16.      public synchronized List<BigInteger> get() {  
          17.          return new ArrayList<BigInteger>(primes);  
          18.      }  
          19. }  

           

          2)中斷

          中斷通常是實現取消最明智的選擇,但線程自身需要支持中斷處理,并且要處理好中斷策略,一般響應中斷的方式有兩種:

          • 處理完中斷清理后繼續傳遞中斷異常(InterruptedException)
          • 調用interrupt方法,使得上層能感知到中斷異常

          3) 取消不可中斷阻塞

          存在一些不可中斷的阻塞,比如:

          • java.io和java.nio中同步讀寫IO
          • Selector的異步IO
          • 獲取鎖

          對于這些線程的取消,則需要特定情況特定對待,比如對于socket阻塞,如果要安全取消,則需要調用socket.close()

          4)JVM的關閉

          如果有任務需要在JVM關閉之前做一些清理工作,而不是被JVM強硬關閉掉,可以使用JVM的鉤子技術,其實JVM鉤子也只是個很普通的技術,也就是用個map把一些需要JVM關閉前啟動的任務保存下來,在JVM關閉過程中的某個環節來并發啟動這些任務線程。具體使用示例如下:

           

          1. public void start() {  
          2.     Runtime.getRuntime().addShutdownHook(new Thread() {  
          3.         public void run() {  
          4.             try { LogService.this.stop(); }  
          5.             catch (InterruptedException ignored) {}  
          6.         }  
          7.     });  
          8. }  

           

          posted on 2011-10-13 16:04 kxbin 閱讀(354) 評論(0)  編輯  收藏 所屬分類: java基礎
          你恨一個人是因為你愛他;你喜歡一個人,是因為他身上有你沒有的;你討厭一個人是因為他身上有你有的東西;你經常在別人面前批評某人,其實潛意識中是想接近他。

          <2025年6月>
          25262728293031
          1234567
          891011121314
          15161718192021
          22232425262728
          293012345

          常用鏈接

          留言簿(5)

          隨筆檔案

          文章分類

          文章檔案

          相冊

          收藏夾

          J2EE

          java技術網站

          Linux

          平時常去的網站

          數據庫

          電影網站

          網站設計

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 德江县| 石嘴山市| 黑山县| 杂多县| 库车县| 郁南县| 吉木萨尔县| 台安县| 丰城市| 渝北区| 平顺县| 远安县| 手游| 斗六市| 莆田市| 赣榆县| 景东| 石家庄市| 无极县| 澄城县| 高要市| 武夷山市| 邢台县| 大宁县| 桂东县| 五寨县| 德昌县| 天台县| 通海县| 吉林省| 油尖旺区| 荥阳市| 昂仁县| 浙江省| 托克逊县| 同江市| 五大连池市| 青铜峡市| 酉阳| 沧州市| 怀柔区|