1. 程式人生 > >【Java併發基礎】利用面向物件的思想寫好併發程式

【Java併發基礎】利用面向物件的思想寫好併發程式

前言

下面簡單總結學習Java併發的筆記,關於如何利用面向物件思想寫好併發程式的建議。面向物件的思想和併發程式設計屬於兩個領域,但是在Java中這兩個領域卻可以融合到一起。在Java語言中,面向物件程式設計的思想能夠讓併發程式設計變得更加簡單。下面將從封裝共享變數、識別共享變數間的約束條件和制定併發訪問策略三方面介紹如何使用面向物件思想去指導編寫併發程式。

封裝共享變數

在併發程式設計中,格外關心的一個重點便是多執行緒對共享變數的訪問問題。我們需要控制好對共享變數的訪問介面。面向物件就有一個非常好的特性:封裝(將屬性和實現細節封裝在物件內部),外界只能通過目標物件提供的公共方法來間接訪問這些內部屬性。使用面向物件的這個特性,就可以非常輕鬆地掌控共享變數的訪問路徑。

利用面向物件思想編寫併發程式的思路:將共享變數作為物件屬性封裝在內部,對所有公共方法定製併發訪問策略。

例如,下面的計數器程式。計數器程式共享變數只有一個,即value,我們把它作為Counter類的屬性,並且將兩個公共方法get()set()宣告為同步方法,這樣Counter類就成為了一個執行緒安全的類了。

public class Counter {
    private long value;
    synchronized long get(){
        return value;
    }
    synchronized long addOne(){
        return ++value;
    }
}

識別共享變數之間的約束條件

識別共享變數之間的約束條件十分重要,因為這會影響到併發訪問策略的定製。

下面舉例說明。
在庫存管理中有個合理庫存的概念,庫存量不能太高,也不能太低,它有一個上限和一個下限。下面使用程式碼說明。
SafeVM中,聲明瞭兩個成員變數upperlower,分別代表了庫存上限和下限,我們使用原子類AtomLong來定義這兩個變數。由於原子類是執行緒安全的,所以這兩個成員變數的set()方法就不需要同步。

public class SafeWM {
    // 庫存上限
    private final AtomicLong upper = new AtomicLong(0);
    // 庫存下限
    private final AtomicLong lower = new AtomicLong(0);

    // 設定庫存上限
    void setUpper(long v){
        upper.set(v);
    }
    // 設定庫存下限
    void setLower(long v){
        lower.set(v);
    }
    // 省略其他業務程式碼
}

但是,我們需要注意,兩個共享變數之間是有一個約束條件的:庫存下限要小於庫存上限。

於是我們就要加入引數校驗,我們在方法setUpper()和方法setLower()中加入檢驗語句:

// 設定庫存上限
void setUpper(long v){
    // 檢查引數合法性
    if (v < lower.get()) {
        throw new IllegalArgumentException();
    }
    upper.set(v);
}
// 設定庫存下限
void setLower(long v){
    // 檢查引數合法性
    if (v > upper.get()) {
        throw new IllegalArgumentException();
    }
    lower.set(v);
}

看似上面的程式碼沒有什麼問題,但是仔細分析一下,便可以發現其實存在競態條件。(校驗的結果依賴執行緒的執行順序)

例如,庫存初始的上限和下限分別為(2,10)。執行緒 A 呼叫 setUpper(5)將上限設定為 5,執行緒 B 呼叫 setLower(7) 將下限設定為 7。執行緒A和執行緒B同時執行,會發現執行緒A和執行緒B都可以同時通過校驗,導致最終庫存為(7,5)。
執行緒A執行時,下限還沒有被執行緒 B 設定,還是 2,而 5>2;執行緒B執行時,上限還沒有被執行緒 A 設定,還是 10,而 7<10。

在沒有識別出庫存下限要小於庫存上限這個約束條件之前,我們制定的併發訪問策略是利用原子類,但是這個策略,完全不能保證庫存下限要小於庫存上限這個約束條件。

所以,在設計階段,我們一定要識別出所有共享變數之間的約束條件,如果約束條件識別不足,很可能導致制定的併發訪問策略南轅北轍。

制定併發訪問策略

指定併發訪問策略,從方案思想上來看,可以從以下三個方面入手:(在前一篇部落格的小結中也提到過)

  1. 避免共享

    上篇文章介紹的執行緒封閉技術。

  2. 不變模式

    例如Actor模式,CSP模式以及函數語言程式設計的基礎都是不變模式。

  3. synchronized同步機制和併發容器

除了以上方案思想,還有一些巨集觀原則需要了解。

  1. 優先使用成熟的工具類

    使用已經設計好的工具類,避免重複造輪子。

  2. 迫不得已才使用低階的同步原語

    低階的同步原語主要指的是 synchronizedLockSemaphore 等,雖然看上去簡單,但使用起來還是要萬分小心。

  3. 避免過早優化

    先保證執行緒安全性,再考慮優化效能。

小結

主要是學習參考[1]時的學習總結筆記,沒有加入太多自己的思考或者補充點,(◞‸◟ )積累還不夠。
要好好擼起袖子加油幹!( ̄^ ̄)ゞ

參考:
[1]極客時間專欄王寶令《Java併發程式設計實戰