併發-Java併發編程基礎

Java併發編程基礎

併發

在計算機科學中,併發是指將一個程序,算法劃分爲若干個邏輯組成部分,這些部分能夠以任何順序進行執行,但與最終順序執行的結果一致。併發能夠在多核操做系統上顯著的提升程序運行速度。java

併發與並行聯繫與區別

這裏參考ErLang之父的解釋,ErLang之父談到了如何向一個5歲小孩解釋併發與並行。
[](算法

直觀來說,併發是兩個等待隊列中的人同時去競爭一臺咖啡機,兩隊列中的排隊者也可能約定交替使用咖啡機,也多是你們同時競爭咖啡機,誰先競爭到咖啡機誰使用,不事後一種的方法可能引起衝突,由於兩個隊列裏面排在隊列首位的人可能同時使用咖啡機),每一個等待者在使用咖啡機以前不只須要知道排在他前面那我的是否已經使用完了咖啡機,還需知道另外一個隊列中排在首位的人是否也正準備使用咖啡機;而並行是每一個隊列擁有本身的咖啡機,兩個隊列之間並無競爭的關係,隊列中的某個排隊者只需等待隊列前面的人使用完咖啡機,而後再輪到本身使用咖啡機。編程

所以,併發意味着多個執行實體(比方說上面例子中的人)可能須要競爭資源(咖啡機),所以就不可避免帶來競爭和同步的問題;而並行則是不一樣的執行實體擁有各自的資源,相互之間可能互不干擾。緩存

爲何咱們很是熱衷於併發編程呢,其實有一個很簡單的緣由,那就是咱們但願本身的程序更快!而後,併發編程倒是一個複雜的主題,若是不能對併發有一個深入的理解,不少時候會拔苗助長,程序不但沒有加快,極可能跑的更慢(好比單核操做系統上跑多個線程,多個線程對某個資源有激烈的競爭等等),更有甚者,還會帶來不少併發產生問題。安全

線程無處不在

在咱們現今的大規模分佈式系統下,到處充滿了多線程,如何良好的應用多線程技術成爲了如今愈來愈大的挑戰.多線程

多線程的優點

  1. 發揮處理器的強大能力(尤爲在CPU核數高的處理器上).
  2. 建模的簡單性(每一個線程處理各自的任務).
  3. 異步事件的簡化處理.
  4. 響應更靈敏的用戶界面

多線程帶來的風險

  1. 線程安全性問題.
    安全性:永遠不會發生糟糕的事情.多線程訪問共享變量在未良好同步的狀況下,產生共享變量的線程安全問題.併發

  2. 活躍性問題.
    活躍性:某件正確的事情最終會發生。
    活躍性的問題:死鎖,飢餓,活鎖.異步

  3. 性能問題.
    性能問題:服務時間過長,響應不靈敏,吞吐率太低,資源消耗太高,可伸縮性低.
    例:線程上下文切換帶來的CPU資源調度問題.分佈式

線程安全

定義:當多個線程訪問某個類時,無論運行時環境採用何種調度方式或者這些線程如何交替執行,而且在主調代碼中不須要任何額外的同步或協同,這個類都能表現出正確的行爲,那麼就稱這個類是線程安全的。(在線程安全類中封裝了必要的同步操做,所以客戶端不須要進一步採起同步措施)函數

重入:當某個線程請求一個由其餘線程持有的鎖時,發出請求的線程就會阻塞,而後因爲內置鎖是可重入的,所以若是某個線程試圖獲取一個已經由本身持有的鎖的時候,那麼這個請求就會成功,重入意味着獲取鎖的粒度是線程,而不是調用。

同步主要做用:
1.線程以前能夠互斥的訪問臨界資源。
2.保證線程的順序性。
3.保證共享變量的可見性。

競態條件

競態條件:因爲多線程不恰當的執行時序而出現不正確的結果.最多見的競態條件類型就是"先檢查後執行"操做,即經過一個可能失效的觀察結果來決定下一步的動做。與大多數併發錯誤同樣,競態條件並不老是會產生錯誤,還須要某種不恰當的執行時序。

對象共享

編寫正確併發程序的關鍵在於:在訪問共享的可變狀態時須要進行正確的管理。

可見性

可見性是一種複雜的屬性,在單線程環境下,向某個共享變量寫入值後而後再去讀,會獲得剛剛寫入的值。但若是是是在多線程環境下,就不必定,線程寫入共享變量以後,另外一個線程再去讀,並不必定會讀到第一個線程寫入的值。爲了保證可見性問題,咱們要正確的使用同步。

java內存模型要求,變量的讀取操做和寫入操做都必須是原子操做,但對於非volatile類型的long和double變量,JVM容許將64位的讀操做或寫操做分解爲兩個32位的操做。在多線程程序中使用共享且可變的long和double等類型的變量也是不安全的,除非使用同步機制將變量保護起來。

可見性與鎖
加鎖的含義不只僅侷限於互斥行爲,還包括內存可見性。爲了確保所用線程都能看到共享變量的最新值,全部執行讀操做或寫操做的線程都必須在同一個鎖上同步。

可見性與volatile
當把變量聲明爲volatile類型以後,編譯器與運行時都會注意到這個變量是共享的,所以不會將該變量上的操做與其餘內存操做一塊兒重排序。volatile變量不會被緩存在寄存器或者其餘處理器不可見的地方,所以在讀取volatile類型的變量時候總會返回最新寫入的值。

volatile 相對synchronized 來講,是一種更輕量級的同步機制。當線程A首先寫入一個volatile變量而且線程B隨後讀取該變量時,在寫入volatile變量以前對A可見的全部變量的值,在B讀取了volatile變量以後,對B也是可見的。所以,從內存可見性的角度來看,寫入volatile變量至關於退出同步代碼塊,而讀取volatile變量就至關於進入同步代碼塊。

不變性

若是某個對象在被建立以後其狀態就不能被修改,那麼這個對象就被稱爲不可變對象。線程安全性是不可變對象的固有屬性之一,他們的不變形條件是由構造函數建立的,只要他們的狀態不改變,那麼這些不變性條件就能得以維持。

當知足如下條件時,對象纔是不可變的。

  1. 對象建立之後其狀態就不能修改。
  2. 對象的全部域都是final類型的。
  3. 對象是正確建立的(this引用沒有逸出).

編寫多線程程序時,一些實用的策略

  1. 線程封閉。線程封閉的對象只能由一個線程擁有,對象被封閉在該線程中,而且只能由這個線程修改。
  2. 只讀共享。在沒有額外同步的狀況下,共享的只讀對象能夠由多個線程併發訪問,但任何線程都不能修改。
  3. 線程安全共享。線程安全的對象在其內部實現同步(封裝線程安全性),所以多個線程能夠經過對象的公有接口來進行訪問而不須要進一步的同步。

關於線程上下文的切換

線程切換上下文須要消耗1-100微妙

參考

  1. java併發編程實戰
  2. https://en.wikipedia.org/wiki/Concurrency_(computer_science)
相關文章
相關標籤/搜索