<address id="ousso"></address>
<form id="ousso"><track id="ousso"><big id="ousso"></big></track></form>
  1. java語言

    Java多線程的開發技巧

    時間:2025-04-21 17:09:12 java語言 我要投稿
    • 相關推薦

    Java多線程的開發技巧

      導語:很多開發者談到Java多線程開發,僅僅停留在new Thread(…).start()或直接使用Executor框架這個層面,對于線程的管理和控制卻不夠深入,下面是 Java多線程的開發技巧,一起來學習下吧:

      不使用線程池的缺點

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

      1. 新建線程的開銷。線程雖然比進程要輕量許多,但對于JVM來說,新建一個線程的代價還是挺大的,決不同于新建一個對象。

      2. 資源消耗量。沒有一個池來限制線程的數量,會導致線程的數量直接取決于應用的并發量,這樣有潛在的線程數據巨大的可能,那么資源消耗量將是巨大的。

      3. 穩定性。當線程數量超過系統資源所能承受的程度,穩定性就會成問題。

      制定執行策略

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

      1. 任務以什么順序執行

      2. 可以有多少個任務并發執行

      3. 可以有多少個任務進入等待執行隊列

      4. 系統過載的時候,應該放棄哪些任務?如何通知到應用程序?

      5. 一個任務的執行前后應該做什么處理

      線程池的類型

      不管是通過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的用法見下面的實例就一目了然了:

      01 public class FutureRenderer {

      02 private final ExecutorService executor = ...;

      03 void renderPage(CharSequence source) {

      04 final ListimageInfos = scanForImageInfo(source);

      05 Callable<list> task =

      06 new Callable<list>() {

      07 public Listcall() {

      08 Listresult

      09 = new ArrayList();

      10 for (ImageInfo imageInfo : imageInfos)

      11 result.add(imageInfo.downloadImage());

      12 return result;

      13 }

      14 };

      15 Future<list> future = executor.submit(task);

      16 renderText(source);

      17 try {

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

      線程的取消與關閉

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

      調用者強制取消。比如一個長時間運行的任務,用戶點擊”cancel”按鈕強行取消

      限時任務

      發生不可處理的任務

      整個應用程序或服務的關閉

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

      1)通過變量標識來控制

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

      01 public class PrimeGenerator implements Runnable {

      02 @GuardedBy("this")

      03 private final Listprimes

      04 = new ArrayList();

      05 private volatile boolean cancelled;

      06 public void run() {

      07 BigInteger p = BigInteger.ONE;

      08 while (!cancelled ) {

      09 p = p.nextProbablePrime();

      10 synchronized (this) {

      11 primes.add(p);

      12 }

      13 }

      14 }

      15 public void cancel() { cancelled = true; }

      16 public synchronized Listget() {

      17 return new ArrayList(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 }


    【Java多線程的開發技巧】相關文章:

    java的多線程09-09

    java多線程08-31

    Java語言程序調試技巧與多線程問題06-14

    java語言的多線程08-29

    java多線程介紹08-23

    java多線程教程11-03

    如何使用java多線程08-23

    Java多線程問題總結10-24

    Java使用多線程的優勢07-10

    <address id="ousso"></address>
    <form id="ousso"><track id="ousso"><big id="ousso"></big></track></form>
    1. 日日做夜狠狠爱欧美黑人