Java多線程學習(七)併發編程中一些問題

我自己總結的Java學習的系統知識點以及面試問題,目前已經開源,會一直完善下去,歡迎建議和指導歡迎Star: https://github.com/Snailclimb/Java-Guide

本節思維導圖:

思維導圖

關注微信公衆號:“Java面試通關手冊” 回覆“Java多線程”獲取思維導圖源文件和思維導圖軟件。

多線程就一定好嗎?快嗎??

併發編程的目的就是爲了能提高程序的執行效率提高程序運行速度,但是併發編程並不總是能提高程序運行速度的,而且併發編程可能會遇到很多問題,比如:內存泄漏、上下文切換、死鎖還有受限於硬件和軟件的資源閒置問題。

多線程就是幾乎同時執行多個線程(一個處理器在某一個時間點上永遠都只能是一個線程!即使這個處理器是多核的,除非有多個處理器才能實現多個線程同時運行)。CPU通過給每個線程分配CPU時間片來實現僞同時運行,因爲CPU時間片一般很短很短,所以給人一種同時運行的感覺。

上下文切換

當前任務在執行完CPU時間片切換到另一個任務之前會先保存自己的狀態,以便下次再切換會這個任務時,可以再加載這個任務的狀態。任務從保存到再加載的過程就是一次上下文切換

上下文切換通常是計算密集型的。也就是說,它需要相當可觀的處理器時間,在每秒幾十上百次的切換中,每次切換都需要納秒量級的時間。所以,上下文切換對系統來說意味着消耗大量的 CPU 時間,事實上,可能是操作系統中時間消耗最大的操作。

Linux相比與其他操作系統(包括其他類 Unix 系統)有很多的優點,其中有一項就是,其上下文切換和模式切換的時間消耗非常少。

那麼我們現在可能會考慮 :如何減少上下文切換的次數呢???

減少上下文切換

減少上下文切換

這是《Java併發編程的藝術》的作者方騰飛大佬嗎????

上下文切換又分爲2種:讓步式上下文切換搶佔式上下文切換。前者是指執行線程主動釋放CPU,與鎖競爭嚴重程度成正比,可通過減少鎖競爭和使用CAS算法來避免;後者是指線程因分配的時間片用盡而被迫放棄CPU或者被其他優先級更高的線程所搶佔,一般由於線程數大於CPU可用核心數引起,可通過適當減少線程數使用協程來避免。

總結一下:

  1. 減少鎖的使用。因爲多線程競爭鎖時會引起上下文切換。
  2. 使用CAS算法。這種算法也是爲了減少鎖的使用。CAS算法是一種無鎖算法。
  3. 減少線程的使用。人物很少的時候創建大量線程會導致大量線程都處於等待狀態。
  4. 使用協程

我們上面提到了兩個名詞:“CAS算法”“協程”。可能有些人不是很瞭解這倆東西,所以這裏簡單說一下。。。

CAS算法

CAS(比較與交換,Compare and swap) 是一種有名的無鎖算法。無鎖編程,即不使用鎖的情況下實現多線程之間的變量同步,也就是在沒有線程被阻塞的情況下實現變量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。實現非阻塞同步的方案稱爲“無鎖編程算法”( Non-blocking algorithm)。
相對應的,獨佔鎖是一種悲觀鎖,synchronized就是一種獨佔鎖,它假設最壞的情況,並且只有在確保其它線程不會造成干擾的情況下執行,會導致其它所有需要鎖的線程掛起,等待持有鎖的線程釋放鎖。

協程

維基百科中的協程

協程也可以說是微線程或者說是輕量級的線程,它佔用的內存更少並且更靈活。很多編程語言中都有協程。Lua, Ruby 等等都有自己的協程實現。Go完全就是因爲協程而發展壯大的。維基百科上面並沒有Java實現協程的方式,但是不代表Java不能實現協程。比如可以使用Java實現的開源協程庫:Quasar。Quasar官網:http://www.paralleluniverse.co/quasar/,。這個庫實現了一種可以和Go語言中的Goroutine相對標的編程概念:Fiber。Fiber是一種真正的協程。

最後Mark兩篇關於協程的文章:

協程,高併發IO終極殺器(3):https://zhuanlan.zhihu.com/p/27590299

次時代Java編程(一):Java裏的協程:http://geek.csdn.net/news/detail/71824

避免死鎖

在操作系統中,死鎖是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。

在線程中,如果兩個線程同時等待對方釋放鎖也會產生死鎖。

鎖是一個好東西,但是使用不當就會造成死鎖。一旦死鎖產生程序就無法繼續運行下去。所以如何避免死鎖的產生,在我們使用併發編程時至關重要。

根據《Java併發編程的藝術》有下面四種避免死鎖的常見方法:

  • 避免一個線程同時獲得多個鎖
  • 避免一個線程在鎖內同時佔用多個資源,儘量保證每個鎖只佔用一個資源
  • 嘗試使用定時鎖,使用lock.tryLock(timeout)來替代使用內部鎖機制
  • 對於數據庫鎖,加鎖和解鎖必須在一個數據庫連接裏,否則會出現解鎖失敗的情況

解決資源限制

這裏我覺得《Java併發編程的藝術》講的還是挺好的。

什麼是資源限制???

所謂資源限制就是我們在進行併發編程時,程序的運行速度受限於計算機硬件資源比如CPU,內存等等或軟件資源比如軟件的質量、性能等等。舉個例子:如果說服務器的帶寬只有2MB/s,某個資源的下載速度是1MB/s,系統啓動10個線程下載該資源並不會導致下載速度編程10MB/s,所以在併發編程時,需要考慮這些資源的限制。硬件資源限制有:帶寬的上傳和下載速度、硬盤讀寫速度和CPU處理速度;軟件資源限制有數據庫的連接數、socket連接數、軟件質量和性能等等。

資源限制引發的問題

在併發編程中,程序運行加快的原因是運行方式從串行運行變爲併發運行,但是如果如果某段程序的併發執行由於資源限制仍然在串行執行的話,這時候程序的運行不僅不會加快,反而會更慢,因爲可能增加了上下文切換和資源調度的時間。

如何解決資源限制的問題

對於硬件資源限制,可以考慮使用集羣並行執行程序。既然單機的資源有限制,那麼就讓程序在多機上運行。比如使用Hadoop或者自己搭建服務器集羣。
對於軟件資源的限制,可以考慮使用資源池將資源複用。比如使用連接池將數據庫和Socket複用,或者在調用對方webservice接口獲取數據時,只建立一個連接。另外還可以考慮使用良好的開源軟件。

在資源限制的情況下如何進行併發編程

根據不同的資源限制調整程序的併發度,比如下載文件程序依賴於兩個資源-帶寬和硬盤讀寫速度。有數據庫操作時,設計數據庫練連接數,如果SQL語句執行非常快,而線程的數量比數據庫連接數大很多,則某些線程會被阻塞,等待數據庫連接。

參考:

維基百科,百度百科

《Java併發編程的藝術》

上下文切換的詳解:http://ifeve.com/context-switch-definition/

如果你覺得博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。

歡迎關注我的微信公衆號:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取)。另外我創建了一個Java學習交流羣(羣號:174594747),歡迎大家加入一起學習,這裏更有面試,學習視頻等資源的分享。

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