Java併發API案例分析之併發設計原理

目錄

0、插播2020CSDN博客之星投票新聞

一、開篇

二、併發與並行

三、併發程序帶來關鍵問題

1、數據競爭

2、死鎖

3、活鎖

4、資源不足

5、優先權反轉

四、Java併發API(詳細)

1、基本併發類

2、同步機制

3、執行器

4、Fork/Join框架

5、併發數據結構

五、併發設計模式

1、信號模型

2、會合模式

3、互斥模式

4、讀寫鎖模式

5、線程池模式

6、線程局部存儲模式

六、最後總結


0、插播2020CSDN博客之星投票新聞

近日(1月11日-1月24日),2020CSDN博客之星評選正在火熱進行中,作爲碼齡1年的小白有幸入選Top 200,首先很感謝CSDN官方把我選上,本來以爲只是來湊熱鬧,看大佬們PK ^_^#。

綜合過去4天大佬們戰況,前十名大佬坐得很穩,不得不令人佩服點贊!真正的實力可以看出,文章數量不重要,更重要的是質量!一切用數據說話,如圖:

截至 2021-01-15 14:40:01

看了大佬的驚人數據,與我差距甚大,不禁感慨,小白只希望進Top 100,希望看到這條新聞的大佬、朋友們、粉絲們、和我一樣的小白們,能頂我一下,爲我投全票,新年一起加油進步,萬事如意!^_^#

投票地址:https://bss.csdn.net/m/topic/blog_star2020/detail?username=charzous

或者掃碼投票:

重點:每一個投票都會被記錄,投了票的後面找Charzous幫忙也容易了(瘋狂暗示投票拉票)

即日起到24號,每天都可以投票哦,票數越多,貢獻排行榜就越靠前,我就記住你的名字啦!

一、開篇

在我之前寫的許多關於Java網絡編程的博文中,已經初步使用了多線程的技術,是java併發的相關應用案例。而現在,需要學習一些關於併發程序設計的原理,弄懂來龍去脈,相對更加深入地理解併發設計原理。而且我發現,前面學習Java網絡編程之後,有了實踐性的理解,再學習其相關原理,比較容易理解原理方面的知識。

這一篇記錄一下我學習的重要知識,整理出來感覺更容易看到,也是方便以後重溫學習查看!

二、併發與並行

很奇妙,併發和並行兩個概念在操作系統的課程上有學習過,當時只是在操作系統層面上理論的學習,未涉及實際應用場景和編程語言。現在重溫知識進一步學習,發現理解起來更實際了。

併發和並行概念有許多定義,整理一下好理解的幾個:

併發是指2個或多個活動在同一時間間隔內發生,在多道程序設計中,是指在同一時間段內有多個程序任務同時運行,也就是宏觀上同時執行,微觀上是串行的。

並行是指在微觀上多個任務同時執行,發生在同一個時間點上。顯然,要求多個程序任務並行運行,就需要多個處理器,在單處理器系統中,只有併發而無並行

 在單個處理器上採用單核執行多個任務即爲併發,操作系統的調度程序會很快從一個任務切換到另一個任務,因此看起來所有任務都是同時運行的。而同一時間在不同處理器或處理器核心上同時執行多個任務,就是並行

最關鍵的還是併發原理以及設計,在併發中一個重要概念“同步”由此引出。

同步是一種協調兩個或多個任務以獲得預期結果的機制。包括:

  1. 控制同步:任務依賴關係。當一個任務的執行需要依賴於另一個任務的結束時,第二個任務不能在前者完成之前開始。
  2. 數據訪問同步:當兩個或多個任務訪問共享變量時,在任意時間裏,只有一個任務可以訪問該變量。

與同步密切相關的一個概念是臨界段,它是一段代碼,用來保證在任意時間內只有一個任務能夠訪問共享資源。而互斥就是用來保證這個臨界段的機制。

三、併發程序帶來關鍵問題

併發程序的編寫設計需要在同步/互斥機制上做許多工作,才能保證併發程序正確執行。所以,需要關注下面幾個關鍵問題。

1、數據競爭

簡單來說,就是多個任務對共享變量進行寫入操作,而沒有同步機制的臨界段作爲約束,程序就存在數據競爭。

public class Account{
    private float balance;
    public void modify(float difference){
        float value=this.balance;
        this.balance=value+difference;
    }
}

 例如,上面的例子,如果存在兩個任務執行同一個Account對象進行modify操作,初始balance=1000,最終應該是3000,但是如果兩個任務同時執行了modify方法的第一條語句,又同時執行第二條語句,結果變爲2000。因此,Account類不是線程安全的,沒有實現原子操作和同步機制。

2、死鎖

簡單來講,就是兩個或多個任務正在等待另一線程釋放的某個資源,而這個線程也正在等待前面的任務釋放的資源,這樣的併發程序就出現了死鎖。

完全滿足以下條件時候,就會導致死鎖。

  1. 互斥條件:死鎖中的資源是不可共享的,一個時間段內只有一個任務可以使用該資源。
  2. 請求和保持條件:任務已經保持了至少一個資源,但又提出新的請求,而該資源需要等待釋放,此時該任務阻塞等待,同時不會釋放自己獲得的資源。
  3. 非剝奪條件:資源只能被那些持有他們的任務釋放。
  4. 循環等待條件:任務1正等待任務2佔用的資源,任務2又正在等待任務3佔用的資源,……,這樣出現循環等待。

處理死鎖的方法有:預防、避免、檢測、解除。這四個基本方法都有具體實現的算法。 

3、活鎖

如果系統中有兩個任務,總是因爲對方的行爲而改變自己的狀態,就會出現活鎖。

舉個栗子,任務1、2都需要資源1、2,此時任務1擁有資源1並加鎖,任務2擁有資源2並加鎖。當他們無法訪問所需資源時候,就釋放自己的資源並開始新的循環,這樣無限持續下去,兩個任務都不會結束自己的執行過程。

4、資源不足

活鎖就是一種資源不足的情況,當任務在系統中無法獲取所需資源繼續執行任務。

解決方法就是要確保公平原則

5、優先權反轉

當一個低優先權任務持有高優先權任務所需資源時,就會發生優先權反轉,低優先權的任務會提前執行。

四、Java併發API(詳細)

java包含了豐富的併發API,在併發程序設計時候可以靈活使用。

1、基本併發類

  1. Thread類:描述了執行併發Java應用程序的所有線程。
  2. Runnable接口:Java中創建併發程序的另一種方式。
  3. ThreadLocal類:用於存放從屬於某個線程的變量。(沒有同步機制時使用)
  4. ThreadFactory接口:實現Factory設計模式的基類,用以創建定製線程。

2、同步機制

Java併發的同步機制支持定義訪問某個共享資源的臨界段;在某一共同點上同步不同的任務。

  1. sychronized關鍵字:可以在一個代碼塊或完整方法中定義一個臨界段。
  2. Lock接口:提供更豐富靈活的同步操作。其中ReentantLock用於實現一個條件關聯鎖;ReentantRead-WriteLock將讀寫操作分離;StampedLock包括了控制讀/寫訪問的模式,是Java8新增的特性。

以下幾個比較陌生:

  1. CountDownLatch類:允許多個任務等待多項操作的結束。
  2. CyclicBarrier類:允許多線程在某個共同點上進行同步。
  3. Phaser類:允許控制那些被分爲多階段的任務的執行。所有任務在完成當前階段分任務前,不能進入下個階段。

3、執行器

  1. Executor接口和ExecutorService接口:包括共有的execute方法。
  2. ThreadPoolExecutor類:可以定義線程池執行器任務的最大數目。
  3. ScheduledThreadPoolExecutor類:一種特殊的執行器,在一段延遲之後執行任務或週期性執行任務。
  4. Callable接口:提供返回值的Runnable接口的代替接口。
  5. Future接口:包含獲取Callable返回值並控制其狀態的方法。

4、Fork/Join框架

該框架定義了一種特殊的執行器,尤其針對分治求解問題,提供了一種優化機制,開銷小。主要類和接口:

  1. ForkJoinPool:實現了用於運行任務的執行器。
  2. ForkJoinTask:可以在上述類中執行的任務。
  3. ForkJoinWorkerThread:準備在1類中執行任務的線程。

5、併發數據結構

Java中常用的數據結構如:ArrayList、Map、HashMap等都不能在併發程序中使用,因爲沒有采取同步機制,不是線程安全的。如果自行採用同步機制,程序計算開銷增大。

因此,Java提供了併發程序中特殊的數據結構,屬於線程安全,主要有兩個類別:

  1. 阻塞型數據結構:包含了阻塞調用任務的方法,比如當獲取值時候該數據結果爲空。
  2. 非阻塞型數據結構:操作可以立即進行,不會阻塞調用任務。

常用的併發數據結構(線程安全):

  1. ConcurrentHashMap:非阻塞哈希表(關鍵字Concurrent開頭的其他數據結構)
  2. ConcurrentLinkedDeque:非阻塞型列表
  3. LinkedBlockQueue:阻塞型隊列
  4. CopyOnWriteArrayList:讀讀共享、寫寫互斥、讀寫互斥。
  • 實現了List接口
  • 內部持有一個ReentrantLock lock = new ReentrantLock();
  • 底層是用volatile transient聲明的數組 array
  • 讀寫分離,寫時複製出一個新的數組,完成插入、修改或者移除操作後將新數組賦值給array

     5.PriorityBlockingQueue:阻塞型隊列,基於優先級對元素進行排序。

     6.AtomicBoolean、AtomicInteger、AtomicLong和AtomicReference:基本Java數據類型的原子實現。(可使用原子變量代替同步)

五、併發設計模式

1、信號模型

實現該模式課採用信號量或者互斥,Java中的ReentrantLock或Semaphore,或者Object類的wait和notify方法。

public void task1(){
    section1();
    commonObject.notify();
}

public void task2(){
    commonObject.wait();
    section2();
}

section2方法總是在section1之後執行。 

2、會合模式

信號模式的推廣,第一個任務將等待第二個任務某個活動的完成,而第二個任務也在等待第一個任務某個活動的完成,區別在於使用到兩個對象。

public void task1(){
    section1_1();
    commonObject1.notify();
    commonObject2.wait();
    section1_2();
}

public void task2(){
    section2_1();
    commonObject2.notify();
    commonObject1.wait();
    section2_2();
}

其中的語句順序不能修改,否則可能出現死鎖。

 section2_2總是在section1_1後執行, section1_2總是在section2_1後執行.

3、互斥模式

互斥機制用以實現臨界段,確保操作相互排斥。

public void task1() {
    preSection();
    try {
        lockObject.lock();//臨界段開始
        section();
    }catch (Exception e){
        ……
    }finally {
        lockObject.unlock();//臨界段結束
        postSection();
    }
}

4、讀寫鎖模式

該模式定義了一種特殊的鎖,包含兩個內部鎖:一個用於讀操作,一個用於寫操作。

鎖的特點:讀讀共享、寫寫互斥、讀寫互斥。

Java併發API的ReentrantReadWriteLock類實現了這種模式

5、線程池模式

該模式應用廣泛,減少了每次爲每個任務創建線程的開銷,它是由一個線程集合和一個待執行任務隊列構成。ExceutorService接口可以實現該模式。

6、線程局部存儲模式

Java中的ThreadLocal類實現了線程局部變量,可以使用線程局部存儲,每個線程會訪問該變量不同實例。

六、最後總結

併發算法設計遵循:

1、使用線程安全的Java併發API

2、在靜態類和共享場合使用局部線程變量

3、避免死鎖:對鎖排序

4、使用原子變量

 

這一篇我記錄了學習併發設計原理的關鍵知識,還有一些常用重要的Java併發編程API,在學習理論知識後,對這個部分有了更深層的理解,如果能夠加上實踐項目或者具體地案例分析,才能算真正掌握。因此,我也找了比較實際的案例進行實踐——文件搜索,將在下一篇的博客進行詳細記錄,希望理論+實踐結合,知識的理解更進一步!

如果覺得不錯歡迎“一鍵三連”哦,點贊收藏關注,有問題直接評論,交流學習!


我的CSDN博客:https://blog.csdn.net/Charzous/article/details/112603639

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章