Java并發(fā)編程實戰(zhàn)——讀后感

未完待續(xù)。

閱讀幫助

本文運用《如何閱讀一本書》的學(xué)習(xí)方法進(jìn)行學(xué)習(xí)。

P15 表示對于書的第15頁。

Java并發(fā)編程實戰(zhàn)簡稱為并發(fā)書或者該書之類的。

熟能生巧,不斷地去理解,就像欣賞一部喜歡的電影,時不時就再看一遍,甚至把劇本下下來通讀。


思想

1、雖然現(xiàn)在都是分布式系統(tǒng),日新月異,但是代碼層面的并發(fā)思想是可以學(xué)習(xí)借鑒的,在不同的層面上使用并發(fā)的設(shè)計理念。

2、本地上的并發(fā)安全加鎖運行起來是比分布式redis鎖快的,比較redis鎖的交互需要通信,本地是代碼層級的執(zhí)行,還有就是用redis鎖主要是我們現(xiàn)在是分布式系統(tǒng),該鎖的作用可能作用的范圍是這幾臺服務(wù)器,用于比如重試、扣庫存之類的,那么加鎖的層級就必須是像redis這樣公用的鎖平臺才行。

3、代碼的執(zhí)行效率問題,如果能在代碼層級范圍來操作并發(fā)執(zhí)行安全的代碼操作,即并發(fā)寫得好,這樣就能提高單臺服務(wù)器的服務(wù)能力,比如1000tps和200tps的區(qū)別,還有就是分布式服務(wù)的線性擴(kuò)展能力,不過這方面應(yīng)該是分布式的架構(gòu)問題,但回到第一個思想上來,我們可以學(xué)習(xí)其思想。

檢視閱讀

1 有系統(tǒng)的略讀(目錄,索引,關(guān)鍵字段等,已完成)

2 粗淺的閱讀(不求甚解的全本閱讀,已完成)

有個不合格的地方是閱讀效率不高,耗費時間太長,不能在有效的時間內(nèi)看完,主動性閱讀只能算及格,還不是很投入式,閱讀的主次不分,不能對需要花費心思理解的部分多花時間加深理解,而對于可以略讀的部分加快閱讀速度(當(dāng)然,對于這種實用技術(shù)書一般都是重點)。

前言部分提供了這本書的各個章節(jié)的大體介紹,每個章節(jié)分屬的部分。

自我要求的4個基本問題

1、這本書在談些什么

什么是并發(fā),為什么要用到并發(fā),以及如何使用并發(fā),特別是編寫性能更好的并發(fā)代碼。還有一部分并發(fā)代碼如何測試的內(nèi)容以及了解jvm的內(nèi)存模型。

2、作者的主要想法、聲明、和論點。

作者想通過這本書教會學(xué)者如何更好更準(zhǔn)確的編寫并發(fā)代碼,關(guān)于并發(fā)代碼的核心思想其實有三個方式解決問題:

一是不在線程間共享狀態(tài)變量,即不用。

1、線程封閉

2、棧封閉(局部變量)

3、ThreadLocal(線程本地變量)類存儲。

最常見的ThreadLocal使用場景為用來解決數(shù)據(jù)庫連接、Session管理 。

二是將狀態(tài)變量修改為不可變的變量,即保證不變性。

滿足以下條件構(gòu)造不變性對象:

1、對象創(chuàng)建以后其狀態(tài)(對象的成員變量、或者說域)就不能修改。(賦值的時候注意對象賦值是賦值地址(引用傳遞),所以要通過copy對象內(nèi)容創(chuàng)建新對象賦值)。

2、對象的域都是final類型。

3、對象是正確構(gòu)建的(在對象的創(chuàng)建期間,this引用沒有逸出)。

當(dāng)多個線程同時訪問同一個資源,并且其中的一個或者多個線程對這個資源進(jìn)行了寫操作,才會產(chǎn)生競態(tài)條件。多個線程同時讀同一個資源不會產(chǎn)生競態(tài)條件

注意:

A(譯者注:注意,“不變”(Immutable)和“只讀”(Read Only)是不同的。當(dāng)一個變量是“只讀”時,變量的值不能直接改變,但是可以在其它變量發(fā)生改變的時候發(fā)生改變。比如,一個人的出生年月日是“不變”屬性,而一個人的年齡便是“只讀”屬性,但不是“不變”屬性。隨著時間的變化,一個人的年齡會隨之發(fā)生變化,而一個人的出生年月日則不會變化。這就是“不變”和“只讀”的區(qū)別。(摘自《Java與模式》第34章)

B 只要不是帶有屬性值的引用類型對象都不會因為原本值改變而導(dǎo)致對象的final域也改變.

三是如果一定要訪問到可變的共享狀態(tài)變量時使用同步,即加鎖同步代碼。

juc并發(fā)包的類和加鎖同步。

四是安全發(fā)布對象

要安全地發(fā)布一個對象,對象的引用以及對象的狀態(tài)(成員屬性)必須同時對其他線程可見。可通過4種方式安全發(fā)布:

  1. 在靜態(tài)初始化函數(shù)中初始化一個對象引用。(或者靜態(tài)初始化塊)

    //靜態(tài)的初始化器,由于在JVM內(nèi)部存在著同步機(jī)制,保證其安全發(fā)布。
    public staitc Holder holder = new Holder(42);
    
  2. 將對象的引用保存在volatile類型的域或者AtomicReferance對象中(可見性)。

  3. 將對象的引用保存到某個正確構(gòu)造對象的final類型域中(不變性)。

  4. 將對象的引用保存到一個由鎖保護(hù)的域中(直接加鎖同步)。

3、這本書是否有道理?有合理性?

有道理且合理,不過有些還沒弄明白,還有就是目前高并發(fā)的技術(shù)不止是在代碼層面的編寫,還有分布式應(yīng)用等。

4、這本書跟我的關(guān)系和意義?

提高我的并發(fā)代碼的理解領(lǐng)悟和編寫能力,理解并發(fā)處理的思想,通過并發(fā)可以更高效地發(fā)揮代碼的效率。

分析閱讀

核心:提出問題,找出答案

一、在談些什么

1、根據(jù)書的種類和主題分類

實用性書,工具書類型。

2、簡短概況本書內(nèi)容

合理的配置線程和編寫并發(fā)代碼可以更高效率地提高多核系統(tǒng)的效率,提高資源利用率和系統(tǒng)吞吐率,而編寫并發(fā)代碼的原則是能不在線程間共享狀態(tài)變量盡量不共享;如果需要共享則最好是不變的狀態(tài)變量;上面這兩種都可以有效避免線程間的問題,實在不能避免,則在編寫并發(fā)代碼時用上jdk api里提供的juc包里各種線程安全類,并注意并發(fā)線程的活躍性、性能、和測試問題。

//待補(bǔ)充

3、按順序和關(guān)聯(lián)性列舉全書大綱(簡介描述)和各個部分大綱

先大后小,先粗后細(xì),搭出骨架后再充填細(xì)的部分,分起碼兩層分部結(jié)構(gòu),一,一之一,一之二等

一、并發(fā)理論基礎(chǔ)及規(guī)則(2-3章)
  1. 避免并發(fā)危險

  2. 構(gòu)造線程安全的類

    加鎖機(jī)制:

    1、內(nèi)置鎖

    同步代碼塊的鎖就是方法調(diào)用所在的對象,該類的實例(所以不同實例可以有多個鎖);靜態(tài)同步方法的鎖是以類Class作為鎖,所以只有一把。

    2、重入

    鎖是可重入的,獲取鎖的操作的粒度是線程而不是調(diào)用(即不是每次調(diào)用就獲取一此鎖,如果該線程已經(jīng)持有了,則他可以重入該方法,如果不可重入,則將因為獲取不到鎖而永遠(yuǎn)停頓下去(已經(jīng)持有了))。

    注意:能不能進(jìn)入某個同步方法取決于是否取到該方法的的鎖,如果是實例鎖對象,則不同實例的方法調(diào)用操作分別獲取不同的鎖,并不會因為是同一個Class類而阻塞,只有靜態(tài)類才有且只有一個鎖對象。

    3、盡量縮小同步代碼塊的作用范圍。

    4、不可能三角:安全性、簡單性和性能是不能同時達(dá)到的,而安全性是必須的,所以簡單些和性能之間存在著相互制約,一定不要盲目地為了性能而犧牲簡單性(可能破壞安全性)。

    5、持有鎖時間過長則會帶來活躍性或性能問題。因此,當(dāng)執(zhí)行時間較長的計算或者無法快速完成的操作(I/O),一定不要持有鎖。

    6、自己構(gòu)建線程安全類和juc類庫來構(gòu)建并發(fā)應(yīng)用程序。

  3. 驗證線程安全

  4. 如何共享和發(fā)布對象?

  5. 發(fā)布和逸出 。當(dāng)把一個對象傳遞給某個外部方法時(public),就相當(dāng)于發(fā)布了這個對象。發(fā)布一個引用出去就可能帶來安全性問題,因為該引用信息逸出了。所以我們使用封裝的最主要原因就是封裝能夠使得對程序的正確性進(jìn)行分析變得可能,減少可能的破壞設(shè)計約束條件。

  6. 線程封閉:不共享數(shù)據(jù),就不會有并發(fā)問題。局部變量(即棧封閉)和ThreadLocal類是線程封閉的,線程封閉是在程序設(shè)計中的一個考慮因素(Swing、JDBC的Connection對象)

二、如何組合線程安全類juc模塊介紹(4-5章)
  1. 線程安全組合

    設(shè)計線程安全類的三個基本要素:

    • 找出構(gòu)成對象狀態(tài)的所以變量。
    • 找出約束狀態(tài)變量的不變性條件。
    • 簡歷對象狀態(tài)(變量)的并發(fā)訪問管理策略(形成正式文檔)。
實例封閉:將數(shù)據(jù)封裝在對象內(nèi)部,可以將數(shù)據(jù)的訪問限制在對象的方法上,從而更容易保證現(xiàn)場在訪問數(shù)據(jù)時總能持有正確的鎖。(即把對象的數(shù)據(jù)訪問收斂到方法上,這樣只需要對訪問該方法加鎖同步就能保證對其訪問的線程安全)
  1. 線程安全容器類
  2. 線程安全同步工具類
三、結(jié)構(gòu)化并發(fā)應(yīng)用程序——如何利用線程提高并發(fā)應(yīng)用程序的吞吐量或響應(yīng)性(6-9章)
  1. 如何識別可并行執(zhí)行的任務(wù),以及如何在任務(wù)執(zhí)行框架中執(zhí)行任務(wù)。
  2. 健壯的并發(fā)應(yīng)用程序的關(guān)鍵——如何實現(xiàn)取消和關(guān)閉線程執(zhí)行任務(wù)。
  3. 線程池的使用
  4. 如何提高單線程子系統(tǒng)的響應(yīng)性——圖形用戶界面應(yīng)用程序
四、活躍性、性能與測試——確保并發(fā)程序執(zhí)行預(yù)期任務(wù),獲得理想性能(10-12章)
  1. 如何避免活躍性故障
  2. 如何提高并發(fā)代碼的性能和可伸縮性。
  3. 測試并發(fā)代碼的技術(shù)
五、高級主題—— (13-16章)
  1. 顯示鎖
  2. 原子變量
  3. 非阻塞算法
  4. 自定義同步工具類

//待補(bǔ)充

4、作者想要我做什么或者想要解決什么問題。

理解和領(lǐng)悟并發(fā)的思想和其優(yōu)缺點,并教我們怎么編寫優(yōu)秀的并發(fā)代碼。

//待補(bǔ)充

二、詮釋這本書內(nèi)容

5、關(guān)鍵字,與作者達(dá)成共識

安全性:永遠(yuǎn)不會發(fā)生糟糕的事情。

活躍性:某件正確的事情最終會發(fā)生。(當(dāng)某個操作無法繼續(xù)執(zhí)行下去時,就會發(fā)生活躍性問題,如死鎖、饑餓、活鎖)

性能:包含多個方面,如服務(wù)時間過長(提供的服務(wù)接口響應(yīng)太慢);響應(yīng)不靈敏;吞吐率過低;資源消耗過高;可伸縮性降低等。

線程、鎖、

競態(tài)條件:由于不恰當(dāng)?shù)膱?zhí)行時序而出現(xiàn)不正確的結(jié)果。最常見的競態(tài)條件類型(先檢查后執(zhí)行:CHECK-THEN-ACT)

最低安全性:線程在沒有同步情況下讀取變量時,可能會得到一個失效值,但至少不是隨機(jī)數(shù),而是之前線程設(shè)置的值,這種安全保證稱為最低安全性。(最低安全性不適用于非volatile類型的64位數(shù)值變量--> double、long,因為jvm運行將64位的讀或?qū)懖僮鞣纸獬蓛蓚€32位的讀或?qū)懖僮鳎?/p>

依賴狀態(tài)的操作:即某個操作包含基于狀態(tài)的先驗條件(如隊列刪除前判空等),那么這個操作就是依賴狀態(tài)的操作。要實現(xiàn)某個等待先驗條件為真時才執(zhí)行的操作,可以通過(Blocking Queue , Semaphore 實現(xiàn))。

//待補(bǔ)充

6、重要句子即作者的主旨

  • 要編寫線程安全的代碼,其核心在于要對狀態(tài)訪問操作進(jìn)行管理,特別是對共享的(shared)和可變的(mutable)狀態(tài)的訪問。對象的狀態(tài)(主要在實例或靜態(tài)域)中的數(shù)據(jù)。
  • 共享:多個線程訪問。
  • 可變:生命周期內(nèi)可以發(fā)生變化。
  • 正確的編程方法:首先使代碼正確運行,然后再提高代碼的速度。(先完成,再優(yōu)化)
  • 線程安全性定義:當(dāng)多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,說明這個類是線程安全的。

無狀態(tài)對象一定是線程安全的。(因為它既不包含任何域,也不包含任何對其他類的類中域的引用。)

  • 盡可能使用現(xiàn)有的線程安全對象(如AtomicLong)來管理類的狀態(tài)。如往無狀態(tài)的類中添加一個有線程安全的對象來管理狀態(tài)的類時,這個類仍然是安全的。
  • 要保持狀態(tài)的一致性,就需要在單個原子操作中更新所有相關(guān)的狀態(tài)變量。(即使這些狀態(tài)變量各自都是線程安全的,也要保證所以的狀態(tài)操作在同一個原子操作中,相當(dāng)于一個事務(wù))
  • 正如“除非需要更高的可見性,否則將所有的域(屬性)都聲明為私有域是一個良好的編程習(xí)慣”,“除非需要某個域是可變的,否則應(yīng)將其聲明為final域”也是一個良好的編程習(xí)慣。
  • 任何線程都可以在不需要額外同步的情況下安全地訪問不可變對象,即使在發(fā)布這些對象時沒有使用同步(疑問:我的理解是這些對象不可變,所以只發(fā)布一次;或者是這些對象內(nèi)部不可變對象的三個條件,所以不會有不確定性)。
  • 實例封閉是構(gòu)建線程安全類的最簡單方式。即:將數(shù)據(jù)封裝在對象內(nèi)部,可以將數(shù)據(jù)的訪問限制在對象的方法上,從而更容易確保線程在訪問數(shù)據(jù)時總能持有正確的鎖。

實例封閉實例:

@ThreadSafe
public class TestBeanSet {
    @GuardedBy("this")
    private final Set<TestBean> mySet = new HashSet<TestBean>();

    public synchronized void addTestBean(TestBean t) {
        mySet.add(t);
    }

    public synchronized boolean containsTestBean(TestBean t) {
        return mySet.contains(t);
    }
}
  • 如果不了解對象的不變性條件(取值不能為負(fù)數(shù))與后驗條件(取值變化范圍在兩個域值的范圍區(qū)間,如范圍的上下界,下界值應(yīng)該小于等于上界值),那么就不能確保線程安全性。要滿足在狀態(tài)變量的有效值或者狀態(tài)轉(zhuǎn)換上的各種約束條件,就需要借助于原子性和封裝性(<u>怎么借助封裝性?</u>)。
  • 基于狀態(tài)的先驗條件判斷的操作稱為依賴狀態(tài)的操作。如判空移除元素等。這種比較多見與生存消費模式下的操作,常見有Blocking Queue 或 Semaphore 。

//待補(bǔ)充

7、找出作者的的論述,總結(jié)作者的想法、看法

歸納法或者演繹法

并發(fā)程序中使用和共享對象時的實用策略,有四:

1、線程封閉。線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,不共享,則線程安全使用對象。

2、只讀共享(即不可變對象)。在沒有額外的同步情況下,共享的只讀對象可以由多個線程并發(fā)訪問,但任何線程都不能修改它(有的情況下可以新建一個不可變對象去替換它)。共享的只讀對象包括不可變對象和事實不可變對象。

3、線程安全共享(經(jīng)過線程安全同步的數(shù)據(jù)結(jié)構(gòu)或者類,如currenthashmap等juc包下的線程安全類)。線程安全的對象在其內(nèi)部實現(xiàn)同步,因此多個線程可以通過對象的公共接口(get/set)來進(jìn)行訪問而不需要進(jìn)一步同步(加鎖等操作)。

4、保護(hù)對象。被保護(hù)的對象只能通過持有特定的鎖來訪問。保護(hù)對象包括封裝在其他線程安全對象中的對象(是什么?),以及已發(fā)布的并且有某個特定鎖保護(hù)的對象(顯示鎖,@GuardedBy)。

//待補(bǔ)充

8、作者要我們這么做的目的(解決了哪些問題)

寫出線程安全的類。

三、評論這本書

前提條件:完成大綱架構(gòu)

在評論時能區(qū)分真正的知識和個人的觀點的不同

批評時要能證明作者的知識不足或錯誤、不合邏輯、分析與理由不完整等。

自己的評論,或者讀后感。

一些知識整理

1、并發(fā)中的單例模式寫法:

單例模式(從雙重加鎖走向延遲初始化占位類模式)

java并發(fā)中的延遲初始化 推薦這個

2、高效是指能在串行性和異步性之間找到合理的平衡。

3、讀取volatile 變量開銷只比讀取非volatile變量的開銷略高一點,因此遠(yuǎn)低于加鎖的變量。volatile的正確使用方式包括:只能確保可見性,以及標(biāo)識程序生命周期事件的發(fā)生(如初始化或關(guān)閉)。還需要尋找一下程序中volatile的使用。

原子變量(atomicInteger等)是一種更好的volatile。

滿足以下三個條件,才應(yīng)該使用volatile變量:

  1. 對變量的寫入操作不依賴變量的當(dāng)前值,或者確保只有單個線程更新變量的值(set方法是synchronized的同步方法)
  2. 該變量不會與其它狀態(tài)變量一起納入不變性條件中。(否則可見性就沒有意義,因為他如果本來就不變,那不需要可見性啊)
  3. 在訪問變量時不需要加鎖。

4、如果想在構(gòu)造函數(shù)中注冊一個事件監(jiān)聽器或啟動線程,可以使用一個私有的構(gòu)造函數(shù)和一個公共的工廠方法。還需要理解下!

疑問解答

1、p1中的粗粒度通信機(jī)制交換數(shù)據(jù),包括:套接字、信號處理器、共享內(nèi)存、信號量以及文件等,里面名稱的意思和通信機(jī)制一般是什么?

2、時間分片(time slicing)?

A:指每個CPU會把其時間進(jìn)行分配,每個程序都有機(jī)會占有一個或多個片段,去執(zhí)行自己的任務(wù)。

3、什么是馮諾依曼計算機(jī)?

4、什么是句柄?內(nèi)存句柄?文件句柄?

5、上下文切換會帶來多大的開銷呢?

6、抽象和封裝會降低程序的性能?是拿來跟面向過程(C)比?

7、volatile的使用?

8、發(fā)布和逸出需要仔細(xì)了解下?p34:不正確構(gòu)造?

9、p25:搞不清楚這兩個同步代碼塊不會因為中間釋放鎖而導(dǎo)致失去鎖,應(yīng)用安全性不能滿足么?

10、為什么final類型的域使用越多,就越能簡化對象可能狀態(tài)的分析過程?或者說final 域為什么能保證是不變的?如果是個fina對象呢,對象里面的屬性也必須是final的才能保證吧?

使用到并發(fā)的開源代碼或組件

1、Swing 的用戶界面框架

2、Servlet

3、RMI(rpc)

4、Swing、JDBC學(xué)習(xí)線程封閉技術(shù)

5、連接池是線程安全的。

參考

《Java并發(fā)編程實戰(zhàn)》

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容