Java併發編程學習筆記(一)

第一部分:基礎知識
第三章:對象的共享
3.2  發佈與溢出
發佈的定義:使得對象能夠在當前作用域之外的代碼中使用。
發佈的注意事項:(1)發佈對象可能會破壞封裝性 (2)難以維持不變性,在構造完成前發佈對象則會破壞線程安全性(引用逃逸)  (3)發佈是連鎖的。即比如我發佈了A對象,假設A對象中能夠通過對非私有變量的引用到達B對象,那麼B對象也會被髮布。  (4)不要在構造方法中使this引用逃逸,比如使用匿名內部類Runnable/Thread 
This引用逃逸的兩個條件:(1)在構造方法中使用內部類,因爲內部類會自動持有外部類的引用,即this (2)使用了內部類後,不要在構造方法中將其發佈。因爲這樣如果在初始化過程中發生了線程上下文切換,那麼有可能看到初始化一半的對象。 
錯誤操作的具體例子:比如將引用保存到一個公有的靜態變量中,因爲靜態變量是全局的。  (2)由public get方法,得到私有的對象。  (3)在構造函數中調用一個可改寫的實例方法(不是private/final方法) (4)
正確的採取措施:(1)使用工廠方法來防止this引用在構造過程中逃逸 (2)在靜態初始化函數中初始化一個對象的引用 (3)將對象的引用保存到用volatile/final修飾的變量;或由鎖保護的域中  (4)在構造函數中可以創建線程,但不要啓動它。http://blog.csdn.net/flysqrlboy/article/details/10607295#


3.3 線程封閉
定義:僅在單線程內訪問數據,就不需要同步,這種技術被稱爲線程封閉。這是實現線程安全的最簡單的技術。
常見應用:Swing的組件都被封裝到EDT(事件分發線程)、JDBC的Connection對象。
常用技術:棧封閉、ThreadLocal類
棧封閉:我們知道,在JVM中的虛擬機棧中維護了方法的局部變量,它們被封閉在執行線程的棧中。但要注意不要被意外發布出去,即逃逸,比如將引用傳遞給靜態變量,或實例中
ThreadLocal:爲每個線程維護一個獨立的變量的副本
不變性對象:注意一點。對象不僅僅要是final的,而且要防止this引用的逃逸。
除非需要使用更高的可見性,否則應該聲明爲私有域;除非需要是可變的,否則應當聲明爲不可變的。 
總結:達到線程安全的幾種措施
1. 使用不可變對象+volatile
2. 棧封閉
3. ThreadLocal(注意不要濫用ThreadLocal,會降低代碼的可重用性,增加耦合度)




第四章:對象的組合


4.2.1 Java監視器模式
1. 定義:這是一種約定。將對象所有可變狀態封裝起來,並由對象自己的內置鎖來保護。
2. 線程安全的檢查:(1)域中的變量是不是滿足不可變性(注意引用逃逸情況)(2)有沒有不安全的“先檢查後執行” (3)如果有其中一種是,就需要通過加鎖機制來達成。如果都滿足,那麼是線程安全的。
3. 疑問:(1)爲什麼4-14可以 4-16不可以,代碼基本一致。區別就是後者在構造方法中賦值初始化,並且是終態的  (2)4-14不行,那爲什麼4-13又可以 也是鎖方法。
4. 回答:因爲4-14定義的字段List是public的,所以其他的可以通過new ListHelper().list來執行操作, 這樣子synchronize修飾的方法相當於鎖的this,那麼可能就不是同一個對象,自然也就鎖不住了。



第五章:基礎構建模塊
1.迭代器快速失敗特性:注意迭代器並不是只有顯式的Iterator纔算是迭代;而包括Object的toString()方法,hashcode()/equals()方法,以及各種的containsAll/removeAll/retainAll都會執行迭代操作。
2. BlockingQueue添加了可阻塞的插入和獲取操作。如果隊列爲空,那麼獲取元素的操作將一直阻塞,直到出現可用元素;如果插入空間已滿,那麼會阻塞到有可用空間。適用於“生產者-消費者”模式。常見情況,線程池和工作隊列。  有一種synchronousQueue的實現,它是一種同步隊列,即生產者產生的產品直接供給消費者使用,而不進入隊列,提高了效率;但他僅適用於有足夠多的消費者且總有一個消費者準備好獲取時使用。
3. 工作密取模式:在“生產者-消費者”模式中,多個消費者競爭同一個阻塞隊列;而在工作密取中,每個消費者都有自己的一個雙端隊列(synchronize和threadlocal);如果其中某一個消費者完成了自己的工作,那麼它還會“祕密”的從其他線程的尾端獲取工作,從而進一步減少競爭情況。
4. InterruptException:處理方式一般是rethrows,即在方法級別重新throws出去;在Runnable的run方法這種不能rethrow的,那就先catch,然後在catch裏面恢復中斷,即catch(exception){Thread.currenThread().Interrupt();} 以前那種粗暴的直接catch是不對的。。。。這樣就喪失了中斷的證據,不利於後續的處理、
5. Callable:類似於Runnable,也可以用於啓動線程。特點是可以返回結果,返回類型爲泛型V,而Runnable的run方法是void的,而且不能向上拋出異常,只能在方法中catch。Callable一般結合線程池Excutor和FutureTask使用。



柵欄和閉鎖:
1. 柵欄(barrier)用於等待線程;閉鎖(latch)用於等待事件。而且閉鎖一旦打開不能關上。而柵欄可以重置,且成功通過柵欄後每個線程會有一個唯一的索引標識號,可以通過這個標識號選出一個領導線程。
2. FutureTask:在Excutor中表示異步任務,屬於閉鎖的一種。通常結合callable和excutor線程池來使用。比如針對一些計算量很大的方法,可以提前加載需要的數據,並行分佈式計算。
3. 信號量:和操作系統講的差不多,主要兩個方法,acquire和release請求和釋放信號量。主要用於數據庫連接池。
4. Cyclicbarrier應用在CPU密集型的時候,開放數量應是CPU的數目或者CPU+1。如果是I/O密集型,則相應的根據密集程度添加數量。
5. Exchanger用於兩個線程間的數據交換信息通信。主要用的就是exchange方法,參數是交換的數據,有一個重載,另一個參數是指定超時的時間。因爲本類是線程安全的,並且在交換過中,如果只有一個線程調用了exchange方法,那麼會阻塞等待另一個交換數據的線程exchang,時間過長就拋出異常。

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerTest {

    private static final Exchanger<String> exgr = new Exchanger<String>();

    private static Thread[] threads=new Thread[2];
    private static ExecutorService threadPool = Executors.newFixedThreadPool(2);

    public static void main(String[] args) {
        threads[0]=new Thread(new Runnable() {
            String test1="飛刀飛刀,我是手雷";
            public void run() {
                try {
                    String ano=exgr.exchange(test1);
                    System.out.println(Thread.currentThread().getName()+ano);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        threads[0].start();
        threads[1]=new Thread(new Runnable() {
            String test2="手雷手雷,我是飛刀";
            public void run() {
                try {
                    String ano= exgr.exchange(test2);
                    System.out.println(Thread.currentThread().getName()+ano);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        });
        threads[1].start();
        System.out.println("交換測試完畢");
    }
}




關於第一部分線程安全概念和規則的總結
1.可變的狀態是判斷是否線程安全的重要參考。可變狀態越少越容易確保線程安全性。全是基本數據類型一定是線程安全的。
2. 儘量將變量聲明爲final的,除非需要它們是可變的
3. 不可變對象一定是線程安全的,這個JVM裏面也提到過,線程安全的最高級別就是不可變對象,所以不可變一定是線程安全的。
4. 用鎖來保護每個可變變量,在保護同一個不變性條件的時候,要使用同一把鎖
5. 在執行復合操作,比如“讀取-修改”操作 的時候要持有鎖
6. 將同步策略文檔化
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章