日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

《Java并发编程实践-第一部分》-读书笔记

發布時間:2023/12/10 java 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Java并发编程实践-第一部分》-读书笔记 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

大家好,我是烤鴨:
《Java并發編程實戰-第一部分》-讀書筆記。

第一章:介紹

1.1 并發歷史:

多個程序在各自的進程中執行,由系統分配資源,如:內存、文件句柄、安全證書。進程間通信方式:Socket、信號處理(Signal Handlers)、共享內存(Shared Memory)、信號量(Semaphores)和文件。

促進因素:

  • 資源利用:等待的時候,其他程序運行會提高效率。
  • 公平:多個用戶或程序可能對系統資源有平等的優先級別。
  • 方便:多個程序各自執行單獨的任務比一個程序執行多個任務方便調度和協調。

早期分時共享系統,每一個進程都是一個虛擬的馮諾依曼機:擁有內存空間、存儲指令和數據,根據機器語言來順序執行指令,通過操作系統的 I/O 實現與外部世界交互。對于每一條指令的執行,都有對 “下一條指令” 的明確定義,并根據程序中的指令集來進行流程控制。

線程允許程序控制流(control flow)的多重分支同時存在于一個進程。共享進程范圍內的資源,比如內存和文件句柄,但是每個線程有自己的程序計數器(program counter)、棧(stack)和本地變量。

1.2 線程優點:

適當多線程降低開發和維護開銷,提高性能。

  • 多核處理器:程序調度基本單元是線程,一個單線程應用程序一次只能運行在一個處理器上。
  • 模型簡化:一個復雜、異步的流程可以被分解為一系列更簡單的同步流程,每一個在相互獨立的線程中運行,只有在特定同步點才彼此交互。比如servlets或者RMI。
  • 異步事件的簡單處理:歷史上,操作系統把一個進程能夠創建的線程限制在比較少的數量(幾百個)。因此,操作系統為多元化的I/O開發了一些高效機制,比如unix的select和poll系統調用,java類庫對非阻塞I/O提供了一組包(java.nio)。
  • 用戶界面的更加響應性:AWT 和 Swing 框架

1.3 線程風險:

  • 安全危險:多線程操作共享變量,有可能指令重排序,會導致發生混亂。
  • 活躍度風險:單線程沒問題,多線程可能出現 死鎖、饑餓、活鎖 (https://blog.csdn.net/qq_22054285/article/details/87911464)。
  • 性能風險:上下文切換可能引起巨大的系統開銷。

1.4 線程無處不在:

比如 AWT和Swing、Timer、Servlets

第二章 線程安全

編寫線程安全的代碼,本質上是管理對狀態的訪問,而且通常都是共享、可變的狀態。

狀態:對象的狀態就是數據存儲在狀態變量,比如實例域或靜態域。共享指多線程訪問、可變指生命周期內可以改變。線程安全的性質,取決于程序中如何使用對象,而不是對象完成了什么。(沒讀懂)

無論何時,只要有多于一個線程訪問給定的狀態變量,而且其中某個線程會寫入該變量,此時必須使用同步來協調線程對該變量的訪問。

Java中首要的同步機制是 synchronized ,提供了獨占鎖。volatile 用來聲明變量可見性,但不能保證原子性。

消除多線程訪問同一個變量的隱患:不跨線程共享變量、使狀態變量不可變、在任何狀態訪問變量使用同步。

設計時考慮線程安全,比后期修復容易。設計線程安全的類,封裝、不可變性以及明確的不變約束會提供幫助。

2.1 線程安全性:

類與它的規約保持一致。良好的規約定義了用于強制對象狀態的不變約束以及描述操作影響的后驗條件。
定義:當多個線程訪問一個類時,如果不用考慮這些線程在運行時環境下的調度和交替執行,并且不需要額外的同步及在調用方代碼不必做其他的協調,這個類行為仍然是正確的,那么這個類是線程安全的。
對于線程安全類的實例進行順序或并發的一系列操作,都不會導致實例處于無效狀態。

  • 示例:一個無狀態(stateless)的servlet

    @ThreadSafe public class StatelessFactorizer implements Servlet{public void Service(ServletRequest req,ServletResponse resp){BigInteger i = extractFormRequest(req);BigInteger[] factors = factor(i);//...} }

    StatelessFactorizer 和 大多數 Servlet 一樣,是無狀態的:不包含域也沒有引用其他類的域。一次特定計算的瞬時狀態,會唯一地存在本地變量中,這些變量存儲在線程的棧中,只有執行線程才能訪問。

    無狀態對象永遠是線程安全的。

2.2 原子性

比如下面的代碼,單線程時運行良好。但是多線程會有問題,++count不是原子操作。
自增操作是"讀-改-寫"操作的實例。

@NotThreadSafe public class StatelessFactorizer implements Servlet{private long count = 0;public void Service(ServletRequest req,ServletResponse resp){BigInteger i = extractFormRequest(req);BigInteger[] factors = factor(i);++ count;//...} }
  • 競爭條件:線程交替執行,會產生競爭,正確的答案依賴"幸運"的時序。最常見的競爭是"檢查再運行"(check-then-act)。比如單例模式,如果沒有double check synchronized,多線程場景就可能出現不止一個對象。

  • 示例:惰性初始化中的競爭條件,延遲對象的初始化,直到程序真正用到它,同時確保只初始化一次。
    (單例模式推薦使用的是double check synchronized)

  • 復合操作:**操作A、B,當其他線程執行B時,要么B全部執行完成,要么一點沒有執行。A、B互為原子操作。**比如前面的自增操作,如果是原子操作的話就沒有問題了。可以考慮Java內置的原子性機制-鎖。

    @ThreadSafe public class StatelessFactorizer implements Servlet{private final AtomicLong count = new AtomicLong(0);public void Service(ServletRequest req,ServletResponse resp){BigInteger i = extractFormRequest(req);BigInteger[] factors = factor(i);count.incrementAndGet();//...} }

    juc 包下包含了原子變量類,實現數字和對象引用的原子狀態轉換。把long類型的計數器替換為 AtomicLong 類型的。

2.3 鎖

比如下面這段代碼,雖然變量本身是線程安全的,但是多個set操作不是原子性的,所以結果是有問題的。
為了保護狀態的一致性,要在單一的原子操作中更新相互關聯的狀態變量。

@NotThreadSafe public class StatelessFactorizer implements Servlet{private final AtomicReference<BigInteger> lastNumber = new AtomicReference<BigInteger>();private final AtomicReference<BigInteger[]> lastFactors = new AtomicReference<BigInteger[]>();public void Service(ServletRequest req,ServletResponse resp){BigInteger i = extractFormRequest(req);if(i.equals(lastNumber.get())){//...}else{BigInteger[] factors = factor(i);lastNumber.set(i);lastFactors.set(factors);}} }
  • 內部鎖:synchronized 塊。
    每個Java對象都可以隱式扮演一個用于同步的鎖角色,這些內置鎖被稱為內部鎖(intrinsic locks)或監視器鎖(monitor locks)。執行線程進入 synchronized 塊之前會自動獲得鎖,無論是正常退出,還是拋出異常,都會在失去 synchronized 塊 的控制時釋放鎖。
    內部鎖在Java中扮演了互斥鎖,只有至多一個線程可以擁有鎖。

    @ThreadSafe public class SynchronizedFactorizer implements Servlet{private BigInteger lastNumber;private BigInteger[] lastFactors;public synchronized void Service(ServletRequest req,ServletResponse resp){BigInteger i = extractFormRequest(req);if(i.equals(lastNumber)){//...}else{BigInteger[] factors = factor(i);lastNumber = i;lastFactors = factors;}} }
  • 重進入 (Reentrancy):當一個線程請求其他線程已經占有的鎖時,請求線程將被阻塞。然而內部鎖時可重進入的,重進入意味著基于“每線程”,而不是“每調用”。(這句話沒看明白,重入說的就是持有鎖的線程再次持有鎖,也就是多線程場景下的單線程場景,每持有鎖一次就重入一次,計數遞增)重進入的實現通過為每個鎖關聯一個請求計數和一個占有它的線程。沒鎖的時候,計數為0,相同的線程每請求一次鎖,計數就會遞增。

2.4 用鎖來保護狀態

鎖使得線程能夠串行地訪問它所保護的代碼路徑,可以用鎖創建相關的協議,保證對共享狀態的獨占訪問。比如 ++count 的遞增或 惰性初始化,操作共享狀態的復合操作必須是原子的。

對于每個可被多個線程訪問的可變狀態變量,如果所有訪問它的線程在執行時都占有同一個鎖,這種情況下,我們稱這個變量由這個鎖保護。

對象的內部鎖和它的狀態之前沒有內在關系。每個共享的可變變量都需要由唯一一個確定的鎖保護,而維護者應該清楚這個鎖。
不是所有數據都需要鎖保護—只有那些被多個線程訪問的可變數據。

對于每一個涉及多個變量的不變約束,需要同一個鎖保護其所有的變量。

2.5 活躍度與性能

上面的代碼 SynchronizedFactorizer 類,在 Service方法加 synchronized,每次只能有一個線程執行它,違背了 Servlet 框架的初衷。多個請求排隊等待并依次被處理。我們把這種Web應用的運行方式描述為弱并發(poor concurrency)的一種表現:限制并發調用數量的,并非可用的處理器資源,而恰恰是應用程序自身的結構。

可以通過縮小 synchronized 塊的范圍來提升并發性。

通過簡單性與性能之間是相互牽制的。實現一個同步策略時,不要過早為了性能而犧牲簡單性(這是對安全性潛在的威脅)。

有些耗時的計算或操作,比如網絡或控制臺 I/O,難以快速完成。這些操作期間不要占有鎖。

第三章 共享對象

synchronized 不僅保證原子性,還保證 內存可見性。

3.1 可見性:讀、寫操作發生在不同線程,“重排序”可能會影響讀線程看到的結果。

在沒有同步的情況下,編譯器、處理器,運行時安排操作的執行順序可能完全出人意料。在沒有進行適當同步的多線程程序中,嘗試推斷那些“必然”發生在內存中的動作時,你總是會判斷錯誤。

  • 過期數據:類似數據庫隔離級別的讀未提交,也就是讀到了更新前的數據。
  • 非原子的64位操作:對于非 volatile的long和double變量,JVM將64位的讀或寫劃分為兩個32位的操作。如果讀、寫在不同的線程,這種情況讀取一個非 volatile類型的long就可能出現一個值的高32位和另一個值的低32位。
  • 鎖和可見性:鎖不僅僅是關于同步與互斥的,也是關于內存可見的。為了保證所有線程都能看到共享的、可變變量的最新值,讀取和寫入線程必須使用公共的鎖進行同步。
  • Volatile 變量:同步的弱形式:確保對一個變量的更新以可預見的方式告知其他線程。當一個域聲明volatile 后,編譯器會監視這個變量,并且對它的操作不會與其他的內存操作一起被重排序。
    只有當 volatile變量能夠簡化實現和同步粗略的驗證時,才使用它們。當驗證正確性必須推斷可見性問題時,應該避免使用volatile變量。正確使用volatile變量的方式:用于確保它們引用的對象狀態的可見性,或者用于標識重要的生命周期事件(比如初始化或關閉)的發生。
    讀取 volatile 變量比 讀取非 volatile 變量的開銷略高。
    加鎖可以保證可見性和原子性;volatile 變量只能保證可見性。

3.2 發布和逸出:

  • 安全構建的實踐:不要讓this引用在構造期間逸出。在構造函數中注冊監聽器或啟動線程,使用私有構造函數和一個公共的工廠方法。

3.3 線程封閉:

類似JDBC連接池的Connection對象,線程總是從池中獲得一個Connection對象,并且用它處理單一請求,最后歸還。這種連接管理模式隱式地將Connection對象限制在處于請求處理期間的線程中。

  • Ad-hoc 線程限制(未經設計的線程封閉行為): 比如使用volatile關鍵字,外加讀寫在同一線程內。

  • 棧限制:棧限制中,只能通過本地變量才能觸及對象。

  • ThreadLocal:線程與持有數值的對象關聯,提供get和set方法。

3.4不可變性:

不可變對象永遠是線程安全的。

  • final:將所有的域聲明為final型,除非它們是可變的。

  • 使用volatile 發布不可變對象:使用可變的容器對象,必須加鎖以確保原子性。使用不可變對象,不必擔心其他線程修改對象狀態,如果更新變量,會創建新的容器對象,不過在此之前任何線程都還和原先的容器打交道,仍然看到它處于一致的狀態。(對應下面的代碼 OneValueCache 和 VolatileCachedFactorizer)

    @Immutable class OneValueCache{private final BigInteger lastNumber;private final BigInteger[] lastFactors;public OneValueCache(BigInteger i,BigInteger[] factors){lastNumber = i;lastFactors = Arrays.copyOf(factors,factors.length);}public BigInteger[] getFactors(BigInteger i) {if(lastNumber == null || !lastNumber.equals(i)){return null;}else{return Arrays.copyOf(lastFactors, lastFactors.length);}} }

3.5 安全發布:

@ThreadSafe public class VolatileCachedFactorizer implements Servlet{private volatile OneValueCache cache = new OneValueCache(null, null);public void service(ServletRequest req, ServletResponse resp){BigInteger i = extractFromRequest(req);BigInteger[] factors = cache.getFactors(i);if (factors == null){facotrs = factors(i);cache = new OneValueCache(i, factors);}//} }
  • 不正確發布:當好對象變壞時,代碼如下:

    public class Holder {private int n;public Holder(int n){this.n = n;}public void assertSanity(){if(n != n){throw new AssertionError("This statement is false.");}} }

    發布線程以外的任何線程都可以看到Holder域的過期值,因而看到的是一個null引用或者舊值。(反之線程同理),這種寫法本身是不安全的。

  • 不可變對象與初始化安全性:即使發布對象引用時沒有使用同步,不可變對象仍然可以被安全地訪問。

    不可變對象可以在沒有額外同步的情況下,安全地用于任意線程;甚至發布它們也不需要同步。

  • 安全發布的模式:
    發布對象的安全可見性。

    • 通過靜態初始化器初始化對象的引用;
    • 將它的引用存儲到volatile域或AtomicReference;
    • 將它的引用存儲到正確創建的對象的final域中;
    • 或者將它的引用存儲到由鎖正確保護的域中。

    線程安全容器。

    ? HashTable、SynchronizedMap、ConcurrentMap、Vector.CopyOnWriteArrayList、CopyOnWriteArraySet、syncronized-List、BlockingQueue或者ConcurrentListQueue

  • 高效不可變對象(Effectively immutable objects):任何線程都可以在沒有額外的同步下安全地使用一個安全發布的高效不可變對象。比如正在維護一個Map存儲每位用戶的最近登錄時間:

    public Map<String,Date> lastLogin = Collections.synchronizedMap(new HashMap<String, Date>)());

    訪問Date值時不需要額外的同步。

  • 可變對象:
    不可變對象可以通過任意機制發布;
    高效不可變對象必須要安全發布;
    可變對象必須要安全發布,同時必須要線程安全或者被鎖保護。

  • 安全地共享對象:

    共享策略:

    • 線程限制:一個線程限制的對象,通過限制在線程中,而被線程獨占,且只能被占有它的線程修改。

    • 共享只讀(shared read-only):一個共享的只讀對象,在沒有額外同步的情況下,可以被多個線程并發訪問,但是任何線程都不能修改它。共享只讀對象包括可變對河與高效不可變對象。

    • 共享線程安全(shared thread-safe) :一個線程安全的對象在內部進行同步,所以其他線程無須額外同步,就可以通過公共接口隨意訪問它。

    • 被守護的(Guarded) :一個被守護的對象只能通過特定的鎖來訪問。被守護的對象包括那些被線程安全對象封裝的對象,和已知被待定的鎖保護起來的已發布的對象。

第四章 組合對象

4.1 設計線程安全的類:

確定對象狀態由哪些變量構成;
確定限制狀態變量的不變約束;
制定一個管理并發訪問對象狀態的策略。

@ThreadSafe public final class Counter {@GuradedBy("this") private long value = 0;public synchronized long getValue(){return value;}public synchronized long increment(){//...return ++value;} }

同步策略定義對象如何協調對其狀態訪問,并且不會違反它的不變約束或后驗條件。

  • 收集同步需求:對象與變量擁有一個狀態空間,即它們可能處于的狀態范圍。狀態空間越小,越容易判斷它們。盡量使用final類型的域,可以簡化對對象可能狀態進行分析。

    比如 Long的區間是 Long.MIN_VALUE 到 Long.MAX_VALUE,后驗條件會指出某種狀態轉換是非法的。比如當前狀態17,下一個合法狀態是18。
    不理解對象的不變約束和后驗條件,就不能保證線程安全性。要約束狀態變量的有效值或者狀態轉換,就需要原子性與封裝性

  • 狀態依賴的操作
    和后驗條件對應的是先驗條件,比如移除隊列中的元素,隊列必須是“非空”狀態。

  • 狀態所有權
    創建的對象歸屬誰所有,所有權意味著控制權,一旦將引用發布到一個可變對象上,就不再擁有獨占的控制權,充其量只可能有"共享控制權"。容器類通常表現出一種"所有權分離"的形式。以 servlet中的 ServletContext 為例。 ServletContext 為 Servlet 提供了類似于 Map的對象容器服務。ServletContext可以調用 setAttribute 和 getAttribute,由于被多線程訪問,ServletContext 必須是線程安全的,而 setAttribute 和 getAttribute 不必是同步的。

4.2 實例限制

將數據封裝在對象內部,把對數據的訪問限制在對象的方法上,更易確保線程在訪問數據時總能獲得正確的鎖。比如 私有的類變量、本地變量或者線程內部的變量。
比如 PersonSet中的 mySet 只能通過 addPerson 和 containsPerson 訪問,而這兩個方法都加了鎖。

@ThreadSafe puclic class PersonSet{@GuardedBy("this")private final Set<Person> mySet = new HashSet<Person>();public synchronized void addPerson(Person p){mySet.add(p);}public synchronized boolean containsPerson(Person p){return mySet.contains(p);} }

使用線程安全的類,分析安全性時更容易。

  • 監視器模式
    Java monitor pattern,遵循Java監視器模式的對象封裝了所有的可變狀態,并由對象自己的內部鎖保護。私有鎖對象可以封裝鎖,客戶代碼無法得到它。共有鎖允許客戶代碼涉足它的同步策略,不正確地使用可能引起活躍度問題。要驗證是否正確使用,需要檢查整個程序,而不是單個類。比如內部方法加 synchronized 。

4.3 委托線程安全

無狀態的類中加入一個 AtomicLong類型的屬性,組合對象安全,因為線程安全性委托給了 AtomicLong。比如使用 ConcurrentHashMap。

  • 非狀態依賴變量

    使用 CopyOnWriteArrayList 存儲每個監聽器清單。

  • 委托無法勝任

    public class NumberRange{// 不變約束: lower <= upperprivate final AtomicInteger lower = new AtomicInteger(0);private final AtomicInteger upper = new AtomicInteger(0);public void setLower(int i){// 警告 —— 不安全的 "檢查再運行"if (i > uppper.get()){throw new IllegalArgumentException("can't set lower to ...");}lower.set(i);}public void setUpper(int i){// 警告 —— 不安全的 "檢查再運行"if (i < lower.get()){throw new IllegalArgumentException("can't set upper to ...");}upper.set(i);}public boolean isInRange(int i){return (i >= lower.get() && i <= upper.get());} }

    NumberRange 不是線程安全的,"檢查再運行"沒有保證原子性。

    底層的AtomicInteger是線程安全的,但是組合類不是,因為狀態變量lower和upper不是彼此獨立的。

    如果一個類由多個彼此獨立的線程安全的狀態變量組成,并且類的操作不包含任何無效狀態轉換時,可以將線程安全委托給這些變量。

  • 發布底層的狀態變量

    如果一個狀態變量是線程安全的,沒有任何的不變約束限制它的值,并且沒有任何狀態轉換限制它的操作,可以被安全發布。

    比如發布一個 public 的AtomicLong變量,沒有上述多余的判斷,就是安全的。

4.4 向已有的線程安全類添加功能

“缺少就加入”,比如list的!contains,再add,由于操作非原子性,可能同一個元素會執行多次add。比如使用Vector可以解決,或者改寫add方法,增加synchronized。

  • 4.4.1 客戶端加鎖

    public classs ListHelper<E>{public List<E> list = Collections.synchronizedList(new ArrayList<E>());// ...public synchronized boolean putIfAbsent(E x) {boolean absent = !list.contains(x);if (absent){list.add(x);}return absent;} }

    上面這個例子對list并不是線程安全的,雖然在方法上加了synchronized。因為鎖的對象不對(客戶端加鎖和外部加鎖所用的不是一個鎖),操作的是list(多線程下對list的操作不是原子性的)。修改如下:

    public classs 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;}} }
  • 4.4.2 組合

    客戶端沒法直接使用list,只能通過 ImprovedList 操作list的方法。客戶端并無關心底層的list是否安全,由ImprovedList的鎖來實現保證就行。

    public classs ImprovedList<T> implements List<T>{public final List<T> list;public ImprovedList(List<T> list){this.list = list;}public synchronized boolean putIfAbsent(E x) {boolean absent = !list.contains(x);if (absent){list.add(x);}return absent;} }
  • 4.5 同步策略的文檔化

    為類的用戶編寫類線程安全性擔保的問答;為類的維護者編寫類的同步策略文檔。

    • 4.5.1 含糊不清的文檔

      比如 Servlet 規范沒有建議任何用來協調對這些共享屬性并發訪問的機制。所以容器代替Web Application所存儲的這些對象應該是線程安全的或者是搞笑不可變的。(容器不可能知道你的鎖協議,需要自己保證這些對象是線程安全的。)

      再比如 DataSource的JDBC Connection,如果一個獲得了JDBC Connection 的及活動跨越了多個線程,那么必須確保利用同步正確地保護到 Connection的訪問。

第五章 構建塊

在實踐中,委托是創建線程安全類最有效的策略之一:只需要用已有的線程安全類來管理所有狀態即可。

5.1 同步容器

同步包裝類,Collections.synchronizedXxx 工廠方法創建。

5.1.1 同步容器之中出現的問題

同步容器都是線程安全的。

public static Object getLast(Vector list) {int lastIndex = list.size() - 1;return list.get(lastIndex); } public static void deleteLast(Vector list) {int lastIndex = list.size() - 1;list.remove(lastIndex); }

不同線程調用 size和get/remove時可能出現 ArrayIndexOutOfBoundsException,對list加鎖可以避免這個問題,但會增加性能開銷。

public static Object getLast(Vector list) {sychroized(list) {int lastIndex = list.size() - 1;return list.get(lastIndex);} } public static void deleteLast(Vector list) {sychroized(list) {int lastIndex = list.size() - 1;list.remove(lastIndex); }}

5.1.2 迭代器和ConcurrentModificationException

無論單線程還是多線程,操作容器,都可能出現ConcurrentModificationException,這是迭代器 Iterator 對修改檢查拋出的異常。

對容器加鎖可以避免這個問題,但會影響性能,替代方法是復制容器,線程隔離操作安全,會有明顯的性能開銷。

5.1.3 隱藏迭代器

容器本身作為一個元素,或者作為另一容器的key時,containsAll、removeAll、retainAll方法以及把容器作為參數的構造函數,都會對容器進行迭代。這些對迭代的間接調用,都可能引起 ConcurrentModificationException。

5.2 并發容器

JUC的并發容器和隊列。

5.2.1 ConcurrentHashMap

5.2.2 Map附加的原子操作

5.2.2 CopyOnWriteArrayList

寫入時復制,為了保證數組內容的可見性(不會考慮后續的修改)

查詢頻率遠高于修改頻率時,適合使用 CopyOnWriteArrayList

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

生產者-消費者模式可以把生產者和消費者的代碼解耦合,但還是通過共享工作隊列耦合在一起。

設計初期就使用阻塞隊列建立對資源的管理。

LinkedBlockingQueue和ArrayBlockingQueue是FIFO隊列,PriorityBlockingQueue是一個按優先級排序的隊列(可以使用Comparator進行排序)。

5.3.3 雙端隊列和竊取工作

雙端隊列和竊取工作(work stealing)模式。一個生產消費者設計中,所有的消費者共享一個工作隊列;在竊取工作的設計中,每一個消費者都有一個自己的雙端隊列。如果一個消費完,可以偷取其他消費者的雙端隊列中的末尾任務。

5.4 阻斷和可中斷的方法

關于 InterruptedException的兩種處理方式:

傳遞 InterruptedException,拋出給調用者。

恢復中斷。捕獲 InterruptedException ,并且在當前線程中調用通過 interrupt 中斷中恢復。

恢復中斷狀態,避免掩蓋中斷:

public class TaskRunnable implements Runnable{BlockingQueue<Task> queue;//...public void run(){try{processTask(queue.take());} catch (InterruptedException e){//恢復中斷狀態Thread.currentThread().interrupt();}} }

5.5 Synchronizer

Synchronizer 是一個對象,包含 信號量(semaphore)、關卡(barrier)以及閉鎖(latch)。

5.5.1 閉鎖

閉鎖(latch)是一種Synchronizer,可以延遲線程的進度直到線程到達終止狀態。閉鎖可以確保特定活動直到其他活動完成后才發生。

  • 資源初始化,使用二元閉鎖可以保證 先后加載。
  • 服務依賴,使用二元閉鎖可以保證 服務的先后啟動。

CountDownLatch 是一個靈活的閉鎖實現。countDown 方法對計數器做減操作,表示一個事件已經發生了,而 await 方法等待計數器打到零,此時所有需要等待的事件都已發生。如果計數器入口值為非零,await會一直阻塞到計數器為零,或者等待線程中斷以及超時。(await一定要設置超時時間)

5.5.2 FutureTask

Future.get() 可以立刻得到返回結果。通常可以把多個FutureTask放到一個list,循環 get()

5.5.3 信號量

計數信號量(Counting semaphore)用來控制能夠同時訪問特定資源的活動的數量,或者同時執行某一給定操作的數量。

一個Semaphore管理一個有效的許可集(permit),活動獲取許可,使用之后釋放。如果沒有可用的許可,acquire會阻塞。

release方法向信號量返回一個許可。信號量的退化形式:二元信號量(互斥)。

比如可以用信號量維護連接池,池為空的時候阻塞,反之解除。

5.5.4 關卡

barrier 類似閉鎖,能夠阻塞線程,關卡和閉鎖關鍵不同在于,所有線程必須同時達到關卡點。閉鎖等待的是事件,關卡等待的是其他線程。

5.6 為計算結果建立高效、可伸縮的告訴緩存

用HashMap和ConcurrentHashMap(同步安全)做內存存儲。

總結:

可變狀態越少,保證線程安全就越容易。

盡量將域聲明為final類型,除非它們的需要是可變的。

不可變對象天數是線程安全的。

封裝使管理復雜度變的更可行。

用鎖來守護每個可變變量。

對同一不變約束中的所有變量都使用相同的鎖。

在運行復合操作期間持有鎖。

在非同步的多線程情況下,訪問可變變量的程序是存在隱患的。

在設計之過程就考慮線程安全,或者在文檔中明確說明非線程安全。

文檔化同步策略。

總結

以上是生活随笔為你收集整理的《Java并发编程实践-第一部分》-读书笔记的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。