Java併發程式設計實戰--筆記一

衣舞晨風發表於2017-06-04

第1-3章(執行緒安全性、物件的共享、物件的組合)

     無狀態物件一定是執行緒安全的。

     要保持狀態的一致性,就需要在單個原子操作中更新所有相關的狀態變數。

     synchronized修飾方法表示:在同一時刻,只有一個執行緒可以執行該方法。

     “重入”意味著獲取鎖的操作的粒度是”執行緒”,而不是呼叫。重入的一種實現方法是,為每一個鎖關聯一個獲取計數值和一個所有者執行緒。當計數值為0時,這個鎖就被認為是沒有被任何執行緒所持有,當執行緒請求一個未被持有的鎖時,JVM將記下鎖的持有者,並且將獲取計數值置為1,如果同一個執行緒再次獲取這個鎖,計數值將遞增,而當執行緒退出同步程式碼塊時,計數器會相應地遞減。當計數值為0時,這個鎖將被釋放。

     如果用同步來協調對某個變數的訪問,那麼在訪問這個變數的所有位置上都需要使用同步。而且,當使用鎖來協調多某個變數的訪問時,在訪問變數的所有位置上都要使用同一個鎖。

     對於可能被多個執行緒同時訪問的可變狀態變數,在訪問它時都需要持有同一個鎖,在這種情況下,我們稱狀態變數是由這個鎖保護。

     加鎖的含義不僅僅侷限於互斥行為,還包括記憶體可見性。為了確保所有執行緒都看到共享變數的最新值,所有執行讀操作或者寫操作的執行緒都必須在同一個鎖上同步。

     不要在構造過程中使this引用溢位
http://blog.csdn.net/jiankunking/article/details/72857031

     ThreadLocal物件通常用於防止對可變的單例項變數或全域性變數進行共享。
假設你需要將一個單執行緒應用程式移植到多執行緒環境中,通過將共享的全域性變數轉換為ThreadLocal物件(如果全域性變數的語義允許),可以維持執行緒安全性。

     ThreadLocal變數類似於全域性變數,它能降低程式碼的可重用性,並在類之間引入隱含的耦合性,因此在使用時要格外小心。

     當滿足以下條件時,物件才是不可變的:

     1、物件建立完之後其狀態就不能修改

     2、物件的所有與都是 final 型別

     3、物件時正確建立的(建立期間沒有 this 的逸出)

     final型別的域是不能修改的,除了這一點外,在Java記憶體模型中,final域還有著特殊的語義,final域能確保初始化過程的安全性,從而可以不受限制地訪問不可變物件,並在共享這些物件時無須同步。具體而言,就是被final修飾的欄位在構造器中一旦被初始化完成,並且構造器沒有把“this”的引用傳遞出去(this引用逃逸是一件很危險的事情,其他執行緒有可能通過這個引用訪問到“初始化了一半”的物件),那麼在其他執行緒中就能看到final欄位的值,而且其外、外部可見狀態永遠也不會改變。它所帶來的安全性是最簡單最純粹的。

     正如“除非需要更高的可見性,否則應將所有的域都宣告為私有域”是一個良好的程式設計習慣,“除非需要某個域是可變的,否則應將其宣告為final域”也是一個良好的程式設計習慣。

     許多人擔心這種方式會帶來效能問題,但這是沒必要的。記憶體分配的開銷比你想象的還要低,並且不可變物件還會帶來其他優勢,例如:減少了對加鎖或者保護性副本的需求,以及降低對基於“代”的垃圾收集機制的影響。

     即使某個物件的引用對其他執行緒是可見的,也並不意味著物件狀態對於使用該物件的執行緒來說一定是可見的。為了確保物件狀態能呈現出一致的檢視,就必須使用同步。

     任何執行緒都可以在不需要額外同步的情況下安全地訪問不可改變物件,即使在釋出這些物件時沒有使用同步。

     安全地釋出一個物件,物件的應用以及物件的狀態必須同時對其他執行緒可見。一個正確構造的物件可以通過以下方式來安全地釋出:

     1、在靜態初始化函式中初始化一個物件引用

     2、將物件的應用儲存到volatile型別的域或者AtomicReferance物件中

     3、將物件的引用儲存到某個正確構造物件的final型別域中

     4、將物件的引用儲存到一個由鎖保護的域中。

     線上程安全容器內部的同步意味著,在將物件放入到某個容器,例如Vector或synchronizedList時,將滿足上述最後一條需求。如果執行緒A將物件X放入一個執行緒安全的容器,隨後執行緒B讀取這個物件,那麼可以確保B看到A設定的X狀態,即便在這段讀/寫X的應用程式程式碼中沒有包含顯式的同步。儘管Javadoc在這個主題上沒有給出很清晰的說明,但執行緒安全庫中的容器類提供了以下的安全釋出保證:

     1、通過將一個鍵或者值放入Hashtable、synchronizedMap或者ConcurrentMap中,可以安全地將它釋出給任何從這些容器中訪問它的執行緒(無論是直接訪問還是通過迭代器訪問)

     2、通過將某個元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中,可以將該元素安全地釋出到任何從這些容器中訪問該元素的執行緒

     3、通過將某個元素放入BlockingQueue或者ConcurrentLinkedQueue中,可以將該元素安全地釋出到任何從這些佇列中訪問該元素的執行緒。

     類庫中的其他資料傳遞機制(例如Future和Exchanger)同樣能實現安全釋出。

     通常,要釋出一個靜態構造的物件,最簡單和最安全的方式是使用靜態的初始化器:

public static Holder holder = new Holder(42);

     靜態初始化器由JVM在類的初始化階段執行。由於在JVM內部存在著同步機制,因此通過這種方式初始化的任何物件都可以被安全地釋出[JLS 12.4.2]。

     物件的釋出需求取決於它的可變性:

     1、不可變物件可以通過任意機制來發布

     2、事實不可改變必須通過安全方式釋出

     3、可變物件必須通過安全方式釋出,並且必須是執行緒安全的或者由某個鎖保護起來、

第4章:物件的組合

     如果在一個不變性條件中包含多個變數,那麼在執行任何訪問相關變數的操作時操作時,都必須持有保護這些變數的鎖。

     將資料封裝在物件內部,可以將資料的訪問限制在物件的方法上,從而更容易確保執行緒在訪問資料時總能持有正確的鎖。

     客戶端加鎖是指:對於使用某個物件X的客戶端程式碼,使用X本身用於保護其狀態的鎖來保護這段客戶程式碼。要使用客戶端加鎖,你必須知道物件X使用的是哪一個鎖。

public class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());
    public boolean putIfAbsent(E x) {
        synchronized(list) {
            boolean absent = !list.contains(x);
            if (absent) {
                list.add(x);
            }
            return absent;
        }
    }
}
//推薦
public class ImprovedList<T> implements List<T> {
    private final List<T> list;
    public ImprovedList(List<T> list) {
        this.list = list;
    }
    public synchronized boolean putIfAbsent(T x) {
        boolean absent = !list.contains(x);
        if (absent) {
            list.add(x);
        }
        return absent;
    }
    //...按照類似的方式將ImprovedList的其他方法實現委託給List
}

     設計階段是編寫設計決策文件的最佳時間。這之後的幾個周或幾個月後,一些設計細節會逐漸變得模糊,因此一定要在忘記之前將她們記錄下來。

Java併發程式設計實戰pdf及案例原始碼下載:
http://download.csdn.net/detail/xunzaosiyecao/9851028

個人微信公眾號:
這裡寫圖片描述

作者:jiankunking 出處:http://blog.csdn.net/jiankunking

相關文章