首頁 > 軟體

建立Java執行緒安全類的七種方法

2022-06-11 14:02:55

前言

幾乎每個 Java 應用程式都使用執行緒。像 Tomcat 這樣的 Web 伺服器在單獨的工作執行緒中處理每個請求,胖使用者端在專用工作執行緒中處理長時間執行的請求,甚至批次處理使用 java.util.concurrent.ForkJoinPool 來提高效能。

因此,有必要以執行緒安全的方式編寫類,這可以通過以下技術之一來實現。

無狀態

當多個執行緒存取同一個範例或靜態變數時,您必須以某種方式協調對該變數的存取。最簡單的方法就是避免使用範例或靜態變數。沒有範例變數的類中的方法只使用區域性變數和方法引數。下面的例子展示了這樣一個方法,它是類 java.lang.Math 的一部分:

public static int subtractExact(int x, int y) {
    int r = x - y;
    if (((x ^ y) & (x ^ r)) < 0) {
        throw new ArithmeticException("integer overflow");
    }
    return r;
}

沒有共用狀態

如果您無法避免狀態,請不要共用狀態。狀態應該只由單個執行緒擁有。這種技術的一個例子是 SWT 或 Swing 圖形化使用者介面框架的事件處理執行緒。

您可以通過擴充套件執行緒類並新增範例變數來實現執行緒區域性範例變數。在以下範例中,欄位 pool 和 workQueue 對於單個工作執行緒是原生的。

package java.util.concurrent;
public class ForkJoinWorkerThread extends Thread {
    final ForkJoinPool pool;                
    final ForkJoinPool.WorkQueue workQueue; 
}

實現執行緒區域性變數的另一種方法是將類 java.lang.ThreadLocal 用於要使執行緒區域性的欄位。下面是一個使用 java.lang.ThreadLocal 的範例變數範例:

public class CallbackState {
public static final ThreadLocal<CallbackStatePerThread> callbackStatePerThread = 
    new ThreadLocal<CallbackStatePerThread>()
   {
      @Override
        protected CallbackStatePerThread  initialValue()
      { 
       return getOrCreateCallbackStatePerThread();
      }
   };
}

您將範例變數的型別包裝在 java.lang.ThreadLocal 中。您可以通過方法 initialValue() 為您的 java.lang.ThreadLocal 提供初始值。

下面展示瞭如何使用範例變數:

CallbackStatePerThread callbackStatePerThread = CallbackState.callbackStatePerThread.get();

通過呼叫 get() 方法,您會收到與當前執行緒關聯的物件。

由於在應用程式伺服器中,使用許多執行緒池來處理請求,因此 java.lang.ThreadLocal 會導致此環境中的記憶體消耗很高。因此,不建議將 java.lang.ThreadLocal 用於由應用程式伺服器的請求處理執行緒執行的類。

訊息傳遞

如果您不使用上述技術共用狀態,則需要一種執行緒進行通訊的方式。做到這一點的一種技術是線上程之間傳遞訊息。您可以使用 java.util.concurrent 包中的並行佇列實現訊息傳遞。或者,更好的是,使用Akka 之類的框架,這是一個演員風格並行的框架。以下範例顯示瞭如何使用 Akka 傳送訊息:

target.tell(message, getSelf());

並收到一條訊息:

@Override
public Receive createReceive() {
     return receiveBuilder()
        .match(String.class, s -> System.out.println(s.toLowerCase()))
        .build();
}

不可變狀態

為了避免傳送執行緒在另一個執行緒讀取訊息時更改訊息的問題,訊息應該是不可變的。因此,Akka 框架的約定是所有訊息都必須是不可變的

當你實現一個不可變類時,你應該將它的欄位宣告為 final。這不僅可以確保編譯器可以檢查這些欄位實際上是不可變的,而且即使它們被錯誤地釋出,也可以使它們正確初始化。這是最終範例變數的範例:

public class ExampleFinalField
{
    private final int finalField;
    public ExampleFinalField(int value)
    {
        this.finalField = value;
    }
}

使用來自 java.util.concurrent 的資料結構

訊息傳遞使用並行佇列進行執行緒之間的通訊。並行佇列是 java.util.concurrent 包中提供的資料結構之一。這個包提供了並行對映、佇列、出隊、集合和列表的類。這些資料結構經過高度優化和執行緒安全測試。

同步塊

如果您不能使用上述技術之一,請使用同步鎖。通過將鎖放在同步塊中,您可以確保一次只有一個執行緒可以執行此部分。

synchronized(lock)
{
    i++;
}

請注意,當您使用多個巢狀同步塊時,可能會出現死鎖。當兩個執行緒試圖獲取另一個執行緒持有的鎖時,就會發生死鎖。

易失性領域

正常的非易失性欄位可以快取在暫存器或快取中。通過將變數宣告為 volatile,您可以告訴JVM和編譯器始終返回最新寫入的值。這不僅適用於變數本身,還適用於執行緒寫入 volatile 欄位的所有值。下面顯示了一個 volatile 範例變數的範例:

public class ExampleVolatileField
{
    private volatile int  volatileField;
}

如果寫入不依賴於當前值,您可以使用 volatile 欄位。或者,如果您可以確保一次只有一個執行緒可以更新該欄位。

總結

到此這篇關於建立Java執行緒安全類的七種方法的文章就介紹到這了,更多相關Java執行緒安全類建立內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com