目錄
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、數據競爭
簡單來說,就是多個任務對共享變量進行寫入操作,而沒有同步機制的臨界段作爲約束,程序就存在數據競爭。
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佔用的資源,任務2又正在等待任務3佔用的資源,……,這樣出現循環等待。
處理死鎖的方法有:預防、避免、檢測、解除。這四個基本方法都有具體實現的算法。
3、活鎖
如果系統中有兩個任務,總是因爲對方的行爲而改變自己的狀態,就會出現活鎖。
舉個栗子,任務1、2都需要資源1、2,此時任務1擁有資源1並加鎖,任務2擁有資源2並加鎖。當他們無法訪問所需資源時候,就釋放自己的資源並開始新的循環,這樣無限持續下去,兩個任務都不會結束自己的執行過程。
4、資源不足
活鎖就是一種資源不足的情況,當任務在系統中無法獲取所需資源繼續執行任務。
解決方法就是要確保公平原則。
5、優先權反轉
當一個低優先權任務持有高優先權任務所需資源時,就會發生優先權反轉,低優先權的任務會提前執行。
四、Java併發API(詳細)
java包含了豐富的併發API,在併發程序設計時候可以靈活使用。
1、基本併發類
- Thread類:描述了執行併發Java應用程序的所有線程。
- Runnable接口:Java中創建併發程序的另一種方式。
- ThreadLocal類:用於存放從屬於某個線程的變量。(沒有同步機制時使用)
- ThreadFactory接口:實現Factory設計模式的基類,用以創建定製線程。
2、同步機制
Java併發的同步機制支持定義訪問某個共享資源的臨界段;在某一共同點上同步不同的任務。
- sychronized關鍵字:可以在一個代碼塊或完整方法中定義一個臨界段。
- Lock接口:提供更豐富靈活的同步操作。其中ReentantLock用於實現一個條件關聯鎖;ReentantRead-WriteLock將讀寫操作分離;StampedLock包括了控制讀/寫訪問的模式,是Java8新增的特性。
以下幾個比較陌生:
- CountDownLatch類:允許多個任務等待多項操作的結束。
- CyclicBarrier類:允許多線程在某個共同點上進行同步。
- Phaser類:允許控制那些被分爲多階段的任務的執行。所有任務在完成當前階段分任務前,不能進入下個階段。
3、執行器
- Executor接口和ExecutorService接口:包括共有的execute方法。
- ThreadPoolExecutor類:可以定義線程池執行器任務的最大數目。
- ScheduledThreadPoolExecutor類:一種特殊的執行器,在一段延遲之後執行任務或週期性執行任務。
- Callable接口:提供返回值的Runnable接口的代替接口。
- Future接口:包含獲取Callable返回值並控制其狀態的方法。
4、Fork/Join框架
該框架定義了一種特殊的執行器,尤其針對分治求解問題,提供了一種優化機制,開銷小。主要類和接口:
- ForkJoinPool:實現了用於運行任務的執行器。
- ForkJoinTask:可以在上述類中執行的任務。
- ForkJoinWorkerThread:準備在1類中執行任務的線程。
5、併發數據結構
Java中常用的數據結構如:ArrayList、Map、HashMap等都不能在併發程序中使用,因爲沒有采取同步機制,不是線程安全的。如果自行採用同步機制,程序計算開銷增大。
因此,Java提供了併發程序中特殊的數據結構,屬於線程安全,主要有兩個類別:
- 阻塞型數據結構:包含了阻塞調用任務的方法,比如當獲取值時候該數據結果爲空。
- 非阻塞型數據結構:操作可以立即進行,不會阻塞調用任務。
常用的併發數據結構(線程安全):
- ConcurrentHashMap:非阻塞哈希表(關鍵字Concurrent開頭的其他數據結構)
- ConcurrentLinkedDeque:非阻塞型列表
- LinkedBlockQueue:阻塞型隊列
- 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