Java併發編程實踐——筆記

Java併發編程實踐

第一章 簡介

1.1 併發簡史

  • 不一樣進程之間能夠經過一些粗粒度的通訊機制進行數據交換,包括:套接字,信號處理器,共享內存,信號量以及文件等
  • 線程容許在同一個進程中同時存在多個程序控制流。線程會共享進程範圍內的資源,例如內存句柄,文件句柄,但每一個線程都有各自的程序計數器,棧以及局部變量等。

1.2 線程帶來的優點

  • 多核處理器
  • 建模簡單
  • 異步事件簡化處理
  • 響應靈敏

1.3 線程帶來的風險

  • 安全性
  • 活躍性

某件正確的事情最終會發生, 活躍性問題包括死鎖、飢餓、活鎖java

  • 性能問題

CPU將更多的時間花在線程調度而不是線程運行上程序員

第二章 線程安全性

編寫線程安全的代碼,核心在於要對狀態訪問操做進行管理,特別是對共享的和可變狀態的訪問算法

共享意味着能夠有多個線程訪問
可變意味着變量的值在其生命週期內能夠發生變化數據庫

Java主要的同步機制包括synchronized關鍵字,volatile變量,顯示鎖以及原子變量編程

若是多個線程訪問同一個可變的變量,且沒有合適的同步,那麼就會出現錯誤
修復手段:
- 不在線程之間共享該變量
- 將狀態變量修改成不可變
- 在訪問狀態變量時使用同步
設計線程安全的類時,良好的面向對象技術,不可修改性,以及明晰的不變性規範都能起到必定的幫助

徹底由線程安全類構成的程序不必定就是安全的,而在線程安全類中也能夠包含非線程安全的類。api

線程安全性是一個在代碼上使用的術語,但它只是與狀態相關的,所以只能應用於封裝其狀態的整個代碼,這可能時一個對象,也可能時整個程序緩存

2.1 什麼是線程安全性

線程安全性的核心概念就是正確性。
正確性的含義時,某個類的行爲與其規範徹底一致。
對於單線程,正確性近似定義爲,所見即所知安全

當多個線程訪問某個類時,這個類始終都能表現出正確的行爲,那麼就稱這個類時線程安全的
當多個線程訪問某個類時,無論運行時採用何種調度方式,或者這些線程將如何交替執行,而且在主調代碼中不須要額外的同步或者協同,這個類都表現出正確的行爲,那麼就稱這個類時線程安全的。網絡

無狀態對象必定是線程安全的多線程

2.2 原子性

++操做時一種緊湊的語法,但實際上操做並不是原子操做。
因爲不恰當的執行時序而出現不正確的結果是一種很是重要的狀況——競態條件(Race condition

2.2.1 競態條件

最多見的競態條件就是先檢查後執行(Check Then Act), 即經過一個可能失效的觀測結果來決定下一步的操做

2.2.2 延遲初始化中的競態條件
@NotThreadSafe
public class LazyInitRace{
    private ExpensiveObject instance = null;

    public ExpensiveObject getInstance(){
        if(instance = null){
            instance = new ExpensiveObject();
        }
        return instance;
    }
}

競態條件並不總會產生錯誤,還須要某種不恰當的執行順序

2.2.3 複合操做

原子操做是指,對於訪問同一個狀態的全部操做,這個操做是以原子方式執行的

2.3 加鎖機制

要保持狀態一致性,就須要在單個原子操做中更新全部相關的狀態變量

2.3.1 內置鎖

java提供了一種內置的鎖機制來支持原子性:同步代碼塊(Synchronized Block)
同步代碼塊包括兩部分

  • 一個做爲鎖的對象引用
  • 一個有這個鎖保護的代碼塊

靜態的synchronized方法以Class對象做爲鎖

synchronized(lock) {
     //訪問或修改由鎖保護的共享狀態
 }

每一個java對象均可以用做一個實現同步的鎖,這些鎖被稱爲內置鎖(Intrinsic Lock)或監視器鎖(Monitor Lock)
線程在進入同步代碼塊以前會自動得到鎖,而且在推出代碼塊時自動釋放鎖,而不論時經過正常的控制路徑退出,仍是經過從代碼塊中拋出異常退出。
得到內置鎖的惟一途徑就時進入由這個鎖保護的同步代碼塊或方法。

2.3.2 重入

重入意味着獲取鎖的操做的粒度時線程,而不是調用

這與pthread(POSIX線程)互斥體默認的枷鎖行爲不一樣,pthread互斥體的獲取操做時以調用爲粒度

public class Widget{
    public synchronized void doSomethind(){

    }
}

public class LoggingWidget extends Widget{
    public synchronized void doSomethind(){
        System.out.println(this.toString() + ": classing doSomething");
        super.doSomething();
    }
}

子類改寫了父類的synchronized方法,若是沒有可重入鎖,那麼這段代碼將產生死鎖。
因爲Widget和LoggingWidget中doSomething方法都時synchronized方法,所以每一個doSomething方法在執行前都會獲取Widget上的鎖,若是鎖時不可重入的,那麼在調用super.doSomething時將永遠沒法得到Widget上的鎖。

2.4 用鎖來保護狀態

對於可能被多個線程同時訪問的可變狀態變量,在訪問它時都須要持有同一個鎖,在這種狀況下,咱們常狀態變量時由這個鎖保護的

對象的內置鎖與其狀態之間沒有內在的關聯,雖然大多數類都將內置鎖用做一種有效的加鎖機制,但對象的域並不必定要經過內置鎖來保護。當獲取與對象關聯的鎖時,並不能阻止其餘線程訪問該對象,某個線程在得到對象的鎖以後,並不能你組織其餘線程訪問該對象,某個線程在得到對象的鎖以後,只能組織其餘線程得到同一個鎖。

每一個共享的和可變的變量都應該只由一個鎖來保護,從而使維護人員知道時哪個鎖。

一種常見的額加鎖約定是,將全部的可變狀態都封裝在對象內部,並經過對對象的內置鎖對全部的可變狀態的代碼路徑進行同步。

當類的不變性條件設計多個狀態變量時,還有另外一個要求:不變性條件中的每一個變量必須由同一個鎖來保護。所以能夠在單個原子操做中訪問或更新這些變量,從而確保不變性條件不被破壞。

2.5 活躍性與性能

兩種不一樣的同步機制不只會帶來混亂,也不會在性能或安全性上帶來任何好處。

當執行時間較長的計算或者可能沒法快速我弄成的操做時(例如,網絡I/O或控制檯I/O),必定不要持有鎖

第三章 對象的共享

3.1 可見性

在在多個線程執行讀寫操做時,咱們沒法保證執行讀操做的線程能適時的看到其餘線程寫入的值。

在沒有同步的狀況下,編譯器、處理器以及運行時均可能對操做的執行順序進行一些意想不到的調整。在缺少足夠同步的多線程程序中,要想對內存操做的執行順序進行判斷,幾乎沒法得出正確結論。
3.1.1 失效數據
3.1.2 非原子的64位操做

非volatile類型的64位數值變量, Java內存模型要求變量的讀取和寫入都是原子操做。對於long和double,虛擬機容許將64位的讀取和寫入分爲兩個32位的操做。 當讀取一個非volatile的long變量時,若是對這個變量的操做在不一樣的線程中執行,那麼極可能會出現錯誤

3.1.3 加鎖與可見性

加鎖的含義不只僅侷限於互斥行爲,還包括內存可見性,爲了確保全部線程都能看到共享變量的最新值

3.1.4 Volatile變量
  • volatile: 編譯器和運行時都會注意到這個變量是共享的,所以不會將該變量的操做與其餘內存操做一塊兒排序
  • volatile時一種比synchronized關鍵字更輕量級的同步機制

volatile在代碼中用來公職狀態的可見性,一般比鎖的代碼更脆弱

volatile的正確用法包括:確保自身狀態的可見性,確保他們所引用對象的狀態的可見性,以及標識一些重要程序生命週期時間的發生

3.2 發佈與逸出

發佈 一個對象的意思是指:時對象可以在當前做用域以外的代碼中使用

如:將一個指向該對象的引用保存早其餘代碼能夠訪問的地方,或者在某一個非私有的方法中返回該引用。或者將引用傳遞到其餘類的方法中。

發佈內部狀態可能會破壞封裝性,並使得程序難以維持不變條件。
若是在對象構造完成以前就發佈該對象,就會破壞線程安全性。當某個不該該發佈的對象被髮布時,這種狀況就被稱爲逸出

假定有一個類C,對於C來講,外部方法(Alien method)是指行爲並不徹底由C來規定的方法,包括其它類中定義的方法以及類中能夠被改寫的方法
當把一個對象傳遞給某個外部方式的時候,就至關於發佈了這個對象。

當某個對象逸出後,你必須假設由某個類或線程可能會無用該對象。這正是須要使用封裝的最主要緣由:封裝可以使得對程序的正確性進行分析變得可能。

public class ThisEscape{
    public ThisEscape(EventSource source){
        source.registerListener(
            new EventListener(){
                public void onEvent(Event e){
                    doSomething(e);
                }
            });
    }
}

ThisEscape發佈EventListener時,也隱含發佈了ThisEscape,由於在這個內部類實例中包含了對ThisEscape實力的隱含引用。

不要在構造過程當中使this逸出

在構造過程當中使this引用逸出的常見錯誤是,在構造函數中啓動一個線程。
若是想在構造函數中註冊一個事件監聽器或啓動線程,那麼可使用一個私有的構造函數和一個公共的工廠方法。

//使用工廠方法防止this引用在構造過程當中逸出
pubilc class SafeListener{
    private final EventListener listener;

    private SafeListener(){
        listener = new EventListener(){
            public void onEvent(Event e){
                doSomething(e);
            }
        }
    }

    public static SafeListener newInstance(EventSource source){
        SafeListener safe = new SafeListener();
        source.registerListener(sfae.listener);
        return sfae;
    }
}

3.3 線程封閉

訪問共享的可變數據時,一般須要使用同步。一種避免使用同步的方法就是不共享數據。
Thread Confinement

java語言中並無強制規定某個變量必須由鎖保護,一樣在java語言中也沒法強制將對象封閉在某個線程中。
java語言提供了一些機制來幫助維持線程封閉性,例如局部變量ThreadLocal類

3.3.1 Ad-hoc線程封閉

Ad-hoc線程封閉是指維護線程封閉性的職責徹底由程序實現來承擔。

可是線程封閉技術的脆弱 程序中儘量少使用

3.3.2 棧封閉

局部變量的固有屬性之一就是封閉在執行線程中。

位置對象引用的棧封閉性時,程序員須要多作一些工做已確保引用不會逸出

3.3.3 ThreadLocal類

ThreadLocal類能使線程中的某個值與保存值的對象關聯起來。每一個線程都存有一份獨立的副本

ThreadLocal對象一般用於防止對可變的單實例變量或局部變量進行共享。

從概念上能夠將ThreadLocal<T> 視爲 Map<Thread,T>,其中保存了特定線程的值。 這些特定於線程的值保存在Thread對象中,當線程終止後,這些值會做爲垃圾回收

ThreadLocal的實現

3.4 不變性

對象不可變必定是線程安全的

知足如下條件對象纔是不可變的

- 對象建立之後其狀態不能修改
- 對象的全部域都是final類型
- 對象時正確建立的(在對象建立期間,this引用沒有逸出)
3.4.1 Final域

在java內存模型中,final域還有着特殊的語義。final域能確保初始化過程的安全性,從而能夠不受限制的訪問不可變對象,並在共享這些對象時無需同步

經過將域聲明爲final類型,也至關於告訴維護人員這些域是不會變化的。

3.4.2 用volatile

3.5 安全發佈

//可見性 致使的不安全發佈
public Holder holder;

public void initialize(){
    holder = new Holder(42);
}
3.5.1 不正確的發佈:正確的對象被破壞
3.5.2 不可變對象域初始化安全性

即便某個對象的引用對其餘線程是可見的,不意味着對象狀態對於使用該對象的線程來講必定是可見的。

任何線程均可以在不須要額外同步的狀況下安全的訪問不可變對象,即便在發佈這些對象的時候沒有使用同步

若是final類型的域所指向的對象是可變的,那麼在訪問這些域所指的對象的狀態是仍須要同步

3.5.3 安全發佈的經常使用模式

能夠經過如下方式來安全的發佈

  • 在靜態初始化函數中初始化一個對象引用

= 將對象的引用保存早volatile類型或者AtomicReferance對象中

  • 將對象的引用保存早某個正確構造對象final類型域中
  • 將對象的引用保存早一個由鎖保護的域中

靜態初始化器由JVM在類的初始化階段執行。因爲在JVM內部存在着同步機制,所以經過這種方式初始化的任何對象均可以被安全的發佈

3.5.3 事實不可變對象

在沒有額外的同步的狀況下,任何線程均可以安全的使用被安全發佈的事實不可變對象

3.5.4 可變對象
不可變對象能夠經過任意機制發佈
事實不可變都西昂必須經過安全方式發佈
可變對象必須經過安全方式來發布,而且必須是線程安全的或者由某個鎖保護起來
3.5.6 安全地共享
  • 線程封閉
  • 只讀共享
  • 線程安全共享
  • 保護對象

第四章 對象的組合

4.1 設計線程安全的類

包含三個基本要素

  • 找出構成對象狀態的全部變量
  • 找出約束狀態變量的不變性條件
  • 創建對象狀態的併發方位管理策略

同步策略定義瞭如何在不違背對象不變性條件

4.1.2 以來狀態的操做
4.1.3 狀態的全部權

許多狀況下 全部與封裝性是相互關聯的:對象封裝它擁有的狀態。反之也成立,即它對它封裝的狀態擁有全部權。

容器類一般便顯出一種全部權分離的狀態, 其中容器類擁有自身的狀態,而客戶代碼則擁有容器中各個對象的狀態。

4.2 實例封閉

Instance confinement
當一個對象封閉到另外一個對象中是,可以訪問被封裝對象的路徑都是可知的。

  • 對象能夠被封閉在實例中
  • 也能夠封閉在做用域中
  • 或者線程內
封閉機制更容易構造線程安全的類,由於當封閉類的狀態時,分析類的線程安全性就無須檢查整個程序

4.3 線程安全性的委託

若是一個類是由多個獨立且線程安全的狀態變量組成,而且在全部的操做中都不包含無效狀態轉換,那麼能夠將線程安全性委託給底層的狀態變量
若是一個狀態變量是線程安全的,而且沒有任何不變性條件來約束它的值,在變量的操做上也不存在任何不容許的狀態轉換,那麼就能夠安全的發佈這個變量

第五章 基礎構建模塊

5.1 同步容器類

同步容器類包括 Vector/ Hashtable 還包括JSK中添加的功能類似的類
他們實現線程安全的方式:將狀態封裝起來,每一個公有方法都進行同步,每次只有一個線程能訪問容器的狀態

5.1.1 同步容器類的問題

併發操做修改容器時可能會出現意料以外的行爲(迭代器操做)

能夠經過客戶端佳做來實現不可靠迭代問題,可是要犧牲掉一些伸縮性。

5.1.2 迭代器與ConcurrentModificationmException

不但願對迭代器加鎖 ---解決辦法 克隆容器, 缺點就是會存在顯著的性能開銷

5.1.3 隱藏的迭代器

編譯器將字符串的鏈接操做轉換爲調用StringBuilder.append(Object)

正如封裝對象的狀態有助於維持不變性條件同樣,封裝對象的同步機制有助於確保實施同步策略

5.2 併發容器

  • ConcurrentHashMap
  • CopyOnWriteArrayList
  • BlockingQue
5.2.1 ConcurrentHashMap

ConcurrentHashMap採用一種更細粒度的加鎖機制——分段所(Locking Striping)
帶來的結果就是併發訪問環境下將實現更高的吞吐量,但線程中損失性能很是小。

ConcurrentHashMap與其餘併發容器一塊兒加強了同步容器類,它們提供的迭代器不會拋出ConcurrentModificationException,所以不須要對容器枷鎖
返回的迭代器具備弱一致性,並不是及時失敗,弱一致性的迭代器能夠容忍併發的修改。
儘管有這些改進,但仍有一些須要權衡的因素,如size/isEmpty, 這些方法的語義被減弱,以反映容器的併發特性。

5.2.2 額外的原子Map操做

put if absent
remove if equal

5.2.3 CopyOnWriteArrayList

5.3 阻塞隊列和生產者消費者模式

5.4 阻塞方法與中斷方法

在代碼中Dion共用一個將拋出InterruptedException異常的方法時,你本身的方法也就變成了一個阻塞方法,

  • 傳遞InterruptedException
  • 恢復中斷
Thread.currentThread().interrupt();

5.5 同步工具類

  • 信號量
  • 柵欄
  • 閉鎖
5.5.1 閉鎖 latch

能夠用來確保某些活動知道其餘活動都完成以後才執行

  • CoundDownLatch
public long timeTasks(int nThreads, final Runnable task) thorw InterruptedException{
    final CountDownLatch startGate = new CountDownLatch();
    final CountDownLatch endGate = new CountDownLatch();

    for (int i = i < nThreads; i++){
        Thread t = new Thread(){
            public void run(){
                try{
                    startGate.await();
                    try{
                        task.run();
                    }finally {
                        endGate.countDown();
                    }
                } catch(InterrupetedException ignored) {}

            }
        }
        t.start();
    }

    long start = System.nanoTIme();
    startGate.countDown();
    endGate.await();
    long end = System.nanoTime();
    return end - start;
}
5.5.2 FUtureTask

FutureTask 表示的計算是經過Callable實現的至關於一種可生成結果的Runnable
FutureTask在Executor框架中表示異步任務

public class Preloader{
    private final FutureTask<ProductInfo> future = new FutureTask<ProductInfo>(new Callable<ProductInfo>(){
            public ProductInfo call() throws DataLoadException{
                return loadProductInfo();
            }
        });
    private final Thread thread = new Thread(future);

    public void start(){
        thread.start();
    }
    
    public ProductInfo get() throws DataLoadException, InterruptedException{
        try{
            return future.get();
        }catch(ExecutionException e){
            Throwable cause = e.getCause();
            if(cause instanceof DataLoadException)
                throw (DateLoadException) cause;
            else
                throw launderThrowable(cause);
        }

    }


}
5.5.3 信號量
public class BoundedHashSet<T>{
    private final Set<T> set;
    private final Semaphore sem;

    public BoundedHashSet(int bound){
        this.get = Collections.synchronizedSet(new HasSet<T>());
        sem = new Semaphore(bound);
    }

    public boolean add(T o) throws InterruptedException{
        sem.acquire();
        boolean wasAdded = false;
        try{
            wasAdded = set.add(o);
            return wasAdded;
        }
        finally {
            if(!wasAdded)
                sem.release();
        }
    }

    public boolean remove(Object o) {
        boolean wasRemoved = set.remove(o);
        if(wasRemoved)
            sem.release();
        return wasRemoved;
    }
}
5.5.4 柵欄

閉鎖時一次性對象,一旦進入終止狀態就不能被重置
Barried相似於閉鎖,區別在於,全部線程必須同時到達柵欄位置才能繼續執行。 閉鎖用域等待時間,柵欄用域等他其餘線程。

當線程到達柵欄位置時,調用await方法,這個方法將阻塞,直到全部線程都到達柵欄位置。若是全部線程都到達了柵欄位置,那麼柵欄將打開。全部線程都被釋放。此時柵欄被重置,以便下次使用。

若是對await調用超市,或者await阻塞的線程被中斷,那麼柵欄就被認爲時打破了。全部阻塞的線程終止,並拋出BrokenBarrierException

CyclicBarrier還會是你將一個柵欄操做傳遞給構造函數,這是一個Runnable,當成功經過柵欄時,會執行它。

public class CellularAutomata{
    private final Board mainBoard;
    private final CyclicBarrier barrier;
    private final Worker[] workers;

    public CellularAutomata(Board board){
        this.mainBoard = board;
        int count = Runtime.getRuntime().availableProcessors();
        this.barrier = new CyclicBarrier(count,
            new Runnable(){
                public void run(){
                    mainBoard.commitNewValues();
                }
            });
        this.workers = new Worker[count];
        for(int i = 0; i < count; i++){
            workers[i] = new Worker(mainBoard.getSubBoard(count,i));
        }
    }

    private class Worker implements Runnable{
        private final Board board;

        public Worker(Board board){
            this.board = board;
        }
        public void run(){
            while (!board.hasConverged()){
                for(int x = 0; x < board.getMaxX();x++){
                    for(int y = 0, y < board.getMaxY(); y++){
                        board.setNewValue(x,y,computeValue(x,y));
                    }
                }
                tyr{
                    barrier.await();
                }catch(InterruptedException e){
                    return;
                }catck(BrokenBarrierException e){
                    return;
                }
            }
        }

    }

    public void start(){
        for(int i = 0; i < workers.length;i++){
            new Thread(worker[i]).start();
        }
        mainBoard.waitForConvergence();
    }
}

第六章 任務執行

#### 6.1.3 無限制建立線程的不足

  • 現成的生命週期的開銷很是高
  • 資源消耗

= 穩定性

### 6.2 Executor框架

public interface Executor{
    void execute(Runnable command);
}

Executor是基於生產者消費者模式

6.2.3 線程池

線程池的優點

- 減少開銷
= 提升響應
- 調整線程池的大小,能夠建立足夠多的線程以是處理器保持忙碌同時還避免了競爭
  • newFixedThreadPool 建立固定長度的線程池
  • newCachedThreadPool 建立一個可緩存的線程池
  • newSingleThreadExecutor 建立一個單線程的Executor
  • newScheduledThreadPool 建立一個固定長度的線程池,並且能夠延遲或定時的方式執行任務,相似於Timer
6.2.4 Executor生命週期
public interface ExecutorService extends Executor{
    void shutdown();
    List<Runnable> shutdownNow();
    boolean isShutdown();
    boolean isTerminated();
    boolean awaitTermination(long timeout, TimeUnit unit) thorws InterruptedException;
}

運行中、關閉、終止

6.2.5 延遲任務與週期任務

6.3 找出可利用的並行性

6.3.2 攜帶結果的任務Callable與Future
6.3.4 在異構任務並行化中存在的侷限

只有當大量相互獨立且同構的任務能夠併發處理時才能體現出將程序的工做負載分配到多個任務中帶來的真正性能提高

第七章 取消與關閉

行爲良好的軟件能很完善的處理失敗、關閉和取消等過程

7.1 任務取消

-在java中沒有一種安全的搶佔式方法來中止線程,所以也就沒有安全的搶佔式方法來中止任務。只有一些協做機制,使請求取消的任務和代碼都遵循一種協商好的協議。

  • 如使用volatile變量
7.1.1 中斷

一般中斷是實現取消最合理的方式

在java的api或語言規範中,並無將中斷與任何取消語義關聯起來,但實際上,若是在取消以外的其餘操做使用中斷,都是不合適的,而且很難支撐其更大的應用???
調用interrupt並不意味着當即中止目標線程正在進行的工做,而只是傳遞了請求中斷的消息
7.1.2 中斷策略

每一個線程擁有各自的中斷策略,所以除非你知道中斷對該線程的含義,不然就不該該中斷這線程

7.1.3 響應中斷
  • 傳遞異常
  • 恢復中斷

只有實現了線程中斷策略的代碼才能夠屏蔽中斷請求,在常規的任務和庫代碼中都不該該屏蔽中斷請求

join 指定線程加入當前線程
yield 暫停當前線程

7.1.4 經過Future實現取消

當Future.get拋出InterruptedException或TimeoutException時,若是你知道再也不須要結果,那麼就能夠調用Future.cancel來取消任務

7.1.6 處理不可中斷的阻塞

並不是全部的可阻塞方法或者阻塞機制都能響應中斷

例如 一個線程因爲執行同步的socket I/O或者等待得到內置鎖而阻塞,那麼中斷請求只能設置線程的中斷狀態,除此以外並無其餘任何做用。

對於那些因爲執行不可中斷操做而被阻塞的線程,可使用相似於中斷的手段來中止這些線程,可是要求咱們必須知道線程阻塞的緣由。

  • Java.io包中的同步socket I/O
  • java.io包中的同步I/O
  • Selector的異步I/O

7.2 中止基於線程的服務

7.2.2 關閉ExecutorService

shutdown正常關閉
shutdownNow 強行關閉

7.3 處理非正常的線程終止

7.4 JVM關閉

正常關閉的觸發方式有多種

  • 當最後一個正常(非守護)線程結束
  • 調用了System.exit時
  • 或者經過其餘特定於平臺的方法
7.4.1 關閉鉤子

在正常關閉中,JVM首相調用全部已註冊的關閉鉤子(Shutdown Hook)

關閉鉤子是指經過Runtime.addShutdownHook註冊的但還沒有開始的線程,能夠用於實現服務或者應用程序的清理工做。
public void start(){
    Runtime.getRuntime().addShutdownHook(new Thread(){
        public void run(){
            try{
                LogService.this.stop();
            }catch(InterruptedException ingnore){}
        }
    });
}
7.4.2 守護線程

Daemon Thread
除了主線程意外,全部的線程都是守護線程(垃圾回收線程和其餘的輔助線程)

普通線程和守護線程的區別在於,當一個線程退出時,JVM會檢查其餘正在運行的線程,若是這些線程都是守護線程,那麼jvm會正常退出操做。
當JVM中止時,全部仍然存在的守護線程將被拋棄,既不會執行finally也不會執行回捲棧,而jvm只是直接退出

7.4.3 終結器

finalize

避免使用終結器

第八章 線程池的使用

8.1 在任務與執行策略之間的隱形耦合

8.1.1 線程飢餓死鎖
8.1.2 運行較長時間的任務

8.2 設置線程的大小

Runtime.availableProcessors

觀察CPU利用水平來設置線程數

N_cpu = number of CPUs
U_cpu = target CPU utilization, 0 <= U_cpu <= 1
W/C = ratio of wait time to compute time

N_threads = N_cpu * U_cpu *(1+W/C)

CPU並非惟一影響線程大小的資源,還報錯內存,文件句柄,套接字句柄和數據庫連接等

8.3 配置ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize, 
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<RUnnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectExecutionHandler handler){……}
8.3.1 線程的建立與銷燬
8.3.2 管理隊列任務
8.3.3 飽和策略
  • AbortPloicy
  • CallerRunsPolicy
  • DiscardPolicy
  • DiscardOldestPolicy
8.3.4 線程工廠

每當線程池須要建立一個線程時,都是經過一個縣城工廠方法來完成的。
默認的線程工廠方法將建立一個新的,非守護的線程,而且不包含特殊的配置信息。

第十章 避免活躍性危險

10.1.1 鎖順序死鎖

若是全部線程以固定的順序來得到鎖,那麼在程序中就不會出現鎖順序死鎖問題

10.1.2 動態的鎖順序死鎖

查看是否存在嵌套的鎖獲取操做

10.1.3 在協做對象之間發生死鎖
10.1.4 開放調用

在調用某個方法是不須要持有鎖,那麼這種調用被稱爲開放調用

經過開放調用來避免死鎖,相似於採用封裝機制提供線程安全的方法

10.1.5 資源死鎖

10.2 死鎖的避免與診斷

10.2.1 支持定時的鎖
10.2.2 經過線程轉儲來分析思索

10.3 其餘活躍性問題

10.3.1 飢餓
10.3.2響應性差
10。3.3活鎖

活鎖一般發生在處理事務消息的應用程序中,若是不能成功地處理某個消息,那麼消息處理機制將會回滾整個事務,並將它從新放在隊列的開頭。會致使重複調用,雖然縣城並無阻塞,可是也沒法繼續進行下去。
一般是因爲過分的錯誤恢復代碼形成的,由於錯誤的將不可修復的錯誤做爲可修復的錯誤

當多個相互協做的線程都對彼此進行響應從而修改各自的狀態,並使得任何一個線程都沒法繼續執行時,就發生了活鎖。
解決這種問題,能夠引入隨機性(Raft 中的隨機性)

第十一章 性能與可伸縮性

可伸縮性指的是,增長計算資源是,程序的吞吐量或者處理能力響應增長

11.3 線程引入的開銷

11.3.1 上下文切換
11.3.2 內存同步
11.3.3 阻塞

11.4 減小鎖的競爭

  • 縮小範圍
  • 縮小鎖的粒度
  • 鎖分段
  • 避免熱點域
  • 一些替代獨佔鎖的方法

原子變量、讀寫鎖

  • 檢測CPU的利用率

第十三章

ReentrantLock
並非一種替代內置加鎖的方法,而時當內置加鎖機制不適用時,做爲一種可選擇的高級功能。

13.1 Lock與ReentrantLock

Lock提供了一種無條件的,可輪詢的、定時的以及可中斷的鎖獲取操做
Lock的視線中必須提供與內部鎖象通的內存可見性語義,但在加鎖語義、調度算法、順序保證以及性能方面有所不一樣

public interface Lock{
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long timeOut, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}
爲何要建立一個與內置鎖如此類似的加鎖機制?
大可能是狀況下,內置鎖均可以知足工做,可是功能上存在侷限性,例如,沒法中斷一個正在等待獲取鎖的線程,沒法在請求獲取一個鎖時,無限等待下去。內置鎖必須在獲取該鎖的代碼塊中釋放,這簡化了編碼工做,而且與異常處理操做實現了很好的交互,但卻沒法實現非阻塞結構的加鎖規則。
Lock lock = new ReentrantLock();
....

lock.lock();
try{
    //doSomethind()
}finally{
    lock.unlock();
}
13.1.1 輪詢鎖與定時鎖

定時鎖與輪詢所提供了避免死鎖發生的另外一種選擇

13.1.2 可中斷的所
public boolean sendOnSharedLine(String message) throws InterruptedException{
    lock.lockInterruptibly();
    try{
        return cancellableSendOnSharedLine(message);
    }finally{
        lock.unlock();
    }

}

private boolean cancellableSendOnSharedLine(String message) throws InterruptedException{
    ...
}
13.1.3 非塊結構的加鎖

13.2 性能

13.3 公平性

公平性將因爲掛起線程和回覆線程時存在的開銷極大下降性能。在實際狀況中,統計上的公平性保證——確保被阻塞的線程最終得到鎖,一般已經夠用

13.4 synchrinized和ReentrantLock之間的選擇
與顯示鎖相比,內置鎖仍然具備很大的優點。內置鎖爲許多開發人員鎖熟悉,而且簡潔緊湊。

在一些內置鎖沒法知足需求的狀況下 ReentrantLcok能夠做爲一種高級工具。當須要一些高級功能是才應該使用ReentrantLock,這些功能包括可定時的,可輪詢的與可中斷的鎖獲取操做,公平隊列以及非塊結構的鎖,不然,仍是應該優先使用synchronized

另外synchronized在線程轉儲中能給出在那些調用幀中得到了哪些鎖,並可以檢測和識別發生死鎖的線程。

synchronized時JVM的內置屬性,它能執行一些優化,例如對線程封閉的鎖對象的鎖消除優化,經過增長鎖的粒度來消除內置鎖的同步。

13.5 讀寫鎖

public interface ReadWriteLock{
    Lock readLock();
    Lock writeLock();
}

讀寫鎖中的一些可選實現

  • 釋放優先
  • 讀線程插隊
  • 重入性
  • 降級
  • 升級

第十四章 構建自定義的同步工具

14.1 狀態依賴性的管理

14.1.3 條件隊列

Object.wait會自動釋放鎖,並請求操做系統掛起當前線程

@ThreadSafe
public class BoundedBuffer<V> extends BaseBoundedBuffer<V>{
 //條件謂詞: not-full(!isFull())
 //條件謂詞: not-emptu(!isEmpty())

 public BoundedBuffer(int size){
     super(size);
 }

 public sunchronized void put(V v) throws InterruptedException{
     while(isFull()){
         wait();
     }
     doPut(v);
     notifyAll();
 }

 public synchronized V take() throws InterruptedException{
     while(isEmpte()){
         wait();
     }
     V v = doTake();
     notifyAll();
     return v;
 }
}

14.2 使用條件隊列

14.2.1 條件謂詞

條件謂詞:使某個操做成爲狀態依賴操做的前提

在有界緩存中,只有當緩存不爲空take方法才能執行, 對於take來講,他的條件謂詞就是緩存不爲空

在條件等待中存在一種重要的三元關係, 包括加鎖,wait方法和一個條件謂詞

每一次wait調用都會隱式的與特定的條件謂詞關聯起來,當調用某個特定的條件謂詞的wait時,調用者必須已經持有與條件隊列相關的鎖,而且這個鎖必須保護着構成條件謂詞的狀態變量
14.2.2 過早喚醒

wait方法的返回並不必定意味着線程正在等待的條件謂詞成真了
內置條件隊列能夠與多個條件謂詞一塊兒使用。

使用條件等待時 如 Object.wait或者 Condition.wait

- 一般都有一個條件謂詞——包括一些對象狀態的測試,線程在執行前必須首先經過這些測試
- 在調用wait以前測試條件謂詞,而且從wait中返回時再次進行測試
- 在一個循環中調用wait
- 確保使用與條件隊列相關的鎖來保護構成條件謂詞的哥哥狀態變量
- 當條用wait、notify、nitofyAll時必定要持有與條件隊列相關的鎖
- 在檢查條件謂詞以後以及開始執行響應的操做以前,不要釋放鎖
14.2.3 丟失的信號

丟失信號是指:線程必須等待一個已經爲真的條件,可是在開始等待以前沒有檢查條件謂詞

14.2.4 通知

因爲在調用notify或nitofyAll時必須持有條件隊列對象的鎖,而若是這些等待中的線程此時不能從新得到鎖,那麼沒法從wait返回,所以發出通知的線程應該儘快釋放鎖,從而確保正在等待的線程儘量快的解除阻塞

只有同時知足如下兩個條件時,才能用單一的nofify而不是notifyAll
- 全部等待線程的類型都象通
- 單進單出

14.3 顯式的Condition對象

Condition是一種廣義的內置條件隊列

public interface Condition{
    void await() throws InterruptedException;
    boolean await(long timeOut, TimeUnit unit) throws InterruptedException;
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    void awaitUniterruptibly();
    boolean awaitUntil(Date deadline) throws InterruptedException;

    void signal();
    void signalAll();

}

Lock.newCondition

@TreadSafe
public class ConditionBoundedBuffer<T>{
    protected final Lock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();

    @GuardBy("lock")
    private final T[] items =(T[]) new Object[BUFFER_SIZE];
    @GuardBy("lock")
    private int tail, head, count;

    public void put(T x) throw InterruptedException{
     lock.lock();
     try{
         while (count == items.length){
             notFull.await();
         }
         items[tail] = x;
         if(++tail == items.length){
             tail = 0;
         }
         ++count;
         notEmpty.signal();
     }finally{
         lock.unlock();
     }
    }

    public T take() throws InterruptedException{
        lock.lock();
        try{
            while(count == 0){
                notEmpty.await();
            }
            T x = items[head]
            items[head] == null;
            if(++head == items.length){
                head = 0;
            }
            --count;
            notFull.signal();
            return x;
        } finally{
            lock.unlock();
        }
    }
}

經過將兩個條件謂詞分別放到兩個等待線程集中

14.4 Synchronizer剖析

實際上 ReentrantLock和 Semaphore的實現都使用了一個共同的基類,即AbstractQueuedSynchronizer(AQS)

AQS是一個用域構建鎖和同步器的框架,許多同步器均可以經過AQS構造

//AQS中獲取操做和釋放操做的基標準形式
boolean acquire() throws InterruptedException{
    while(當前狀態不容許獲取操做){
        if(須要阻塞獲取請求){
            若是當前線程不在隊列中,則將其插入隊列
            阻塞當前線程
        }
        else
            返回失敗
    }
    可能更新同步器的狀態
    若是線程位於隊列中,則將其移出隊列
    返回成功
}

void release(){
    更新同步器的狀態
    if(新的狀態容許某個被阻塞的線程獲取成功)
        解除隊列中一個或多個線程的阻塞狀態
}

第15章 原子變量與非阻塞同步機制

15.1 鎖的劣勢

15.2 硬件對併發的支持

15.1 CAS

CAS的主要缺點是, 它將使調用者處理競爭問題,而鎖能自動處理競爭問題

15.2.3 JVM對CAS的支持

java.util.concurrent中的大多數類在實現時直接或間接的使用了這些原子變量類

15.3 原子變量類

  • AtomicInteger
  • AtomicLong
  • AtomicBoolean

15.4 非阻塞算法

基於鎖的算法可能會發生各類活躍性故障,若是線程在持有鎖時因爲阻塞I/O,內存也確實或其餘延遲致使推遲執行,那麼頗有可能其餘線程都不能繼續執行。
若是在某種算法中,一個線程的失敗或掛起不會致使其餘線程也失敗或掛起,那麼這種算法就被稱爲非阻塞算法。

非阻塞算法一般不會出現死鎖和優先級反轉的問題,可是可能會出現飢餓和活鎖問題。

15.4.1 非阻塞棧
@ThreadSafe

public class ConcurrentStack <E> {
    AtomicReference<Node<E>> top = new AtomicReference<Node<E>>();
    public void push(E item){
        Node<E> new Head = new Node<E>(item);
        Node<E> oldHead;
        do{
            oldHead = top.get();
            newHead.next = oleHead;
        } while (!top.compareAndSet(old,newHead));

    }

    public E pop(){
        Node<E> oldHead;
        Node<E> newHead;
        do{
            oldHead = top.get();
            if(oldHead == null){
                return null;
            }
            newHead = oldHead.next;
        } while(!top.compareAndSet(oldHead, newHead));
    }

    private static Node<E>{
        public final E item;
        public Node<E> next;
        public Node(E item){
            this.item = item;

        }
    }
}

第十六章 Java內存模型

Java語言規範要求JVM在線程中維護一種相似串行的語義:只要程序的最終結果與在嚴格串行的環境中執行結果相同,那麼上述全部操做都是容許的。
JMM規定了JVM必須遵循一組最小保證,這組保證規定了對變量的寫入操做再合適將對於其餘線程可見。

16.1.1 平臺的內存模型
16.1.2 重排序
16.1.3 Java內存模型簡介

java內存模型時經過各類操做來定義的,包括對變量的讀/寫操做,監視器的加鎖和釋放慚怍,以及線程的啓動和合並操做,JMM位程序中全部的操做定義了一個偏序關係
稱之爲Happens-before
當一個變量被多線程讀取,而且至少被一個線程寫入時,若是在取操做和寫操做之間沒有依照Happens-before來排序,那麼就會產生數據競爭問題。

  • 程序順序規則
  • 監視器鎖規則
  • volatile變量規則
  • 線程啓動規則
  • 線程結束規則
  • 終端規則
  • 終結器規則
  • 傳遞性

見jvm筆記

16.2 發佈

除了不可變對象意外,使用被另外一個線程初始化的對象一般都是不安全的,除非對象的發佈操做是在使用該對象的線程開始使用以前執行。

16.2.2 安全發佈
  • 延遲初始化
  • 提早初始化
  • 雙重檢查加鎖(Double Lock Check)

16.3 初始化過程當中的安全性

初始化安全性將確保對於被正確構造的對象,全部線程都能看到有構造函數位對象給各個final域設置的正確值,而無論採用何種方式來發布對象。並且,對於能夠經過被正確構造對象中某個final域到達的任意變量,將一樣對於其餘線程可見。

對於含有final域的對象,初始化安全性能夠防止對象的初始引用被重排序到構造過程以前。當構造函數完成時,構造函數對final域的全部寫入操做,以及對經過這些域能夠到達的任何變量的寫入操做,都將被凍結。而且任何得到該對象引用的線程至少能確保看到被凍結的值。

初始化安全性只能保證經過final域可達的值從構造過程完成時開始的可見性。對於經過非final域可達的值,或者在構成過程完成後可能改變的值,必須採用同步來確保可見性。
相關文章
相關標籤/搜索