java 多線程基礎(一)

同步與異步

    同步和異步的概念對於不少人來講是一個模糊的概念。其實咱們的生活中存在着不少同步異步的例子。好比:你叫我去吃飯,我聽到了就馬上和你去吃飯,若是咱們有聽到,你就會一直叫我,直到我聽見和你一塊兒去吃飯,這個過程叫同步;異步過程指你叫我去吃飯,而後你就去吃飯了,而無論我是否和你一塊兒去吃飯。安全

  • 同步交互:指發送一個請求,須要等待返回,而後纔可以發送下一個請求,有個等待過程;多線程

  • 異步交互:指發送一個請求,不須要等待返回,隨時能夠再發送下一個請求,即不須要等待。併發

線程與進程

    進程就是一個應用程序在處理機上的一次執行過程,它是一個動態的概念,而線程是進程中的一部分,進程包含多個線程在運行。以下是網上看到的一個很好的例子異步

    線程與進程我的的理解比如地鐵5號線中的一條線路的阻塞會影響整段5號線的運行。間接的也會影響到其餘地鐵線路的阻塞。進程比如站點(國貿站,大望路站等),線程比如線路一個站點能夠有多條線路,站點不是活動的實體是沒有能力調配整套地鐵資源的。只有中央系統才能統一操做活動的實體。spa

    進程做爲地點基本單位,線程做爲獨立運行和路線調度的基本單位,不須要擁有資源(如廁所,空調,美化等),因此付出的開銷就比較小,多個線路的的通行能更高效提升系統。每條線路有本身設計空間和目標站點就也是所說執行上下文。子進程和父進程有不一樣的車站(5號線和1號線東單站),而多個線路可共享同一個站點。線程

並行與併發

  • 併發:實質是一個物理CPU(也能夠多個物理CPU) 在若干道程序之間多路複用,併發性是對有限物理資源強制行使多用戶共享以提升效率。設計

  • 並行:性指兩個或兩個以上事件或活動在同一時刻發生。在多道程序環境下,並行性使多個程序同一時刻可在不一樣CPU上同時執行。

併發,是在同一個cpu上同時(不是真正的同時,而是看來是同時,由於cpu要在多個程序間切換)運行多個程序。code

 

併發

並行,是每一個cpu運行一個程序。進程

併發

打個比方。併發,就像一我的(cpu)喂2個孩子(程序),輪換着每人喂一口,表面上兩個孩子都在吃飯。並行,就是2我的喂2個孩子,兩個孩子也同時在吃飯。事件

臨界區

    臨界區(Critical Section)是一段供線程獨佔式訪問的代碼,也就是說如有一線程正在訪問該代碼段,其它線程想要訪問,只能等待當前線程離開該代碼段方可進入,這樣保證了線程安全。他工做於用戶級(相對於內核級),在Window系統中CRITICAL_SECTION實現臨界區相關機制。

阻塞與非阻塞

    阻塞與非阻塞一般來形容多線程間的相互影響。好比一個線程佔用了臨界區資源,那麼其餘全部須要這個資源的線程就必須在這個臨界區中進行等待。等待會致使線程掛起,這種狀況就是阻塞,此時,若是佔用資源的線程一直不肯意釋放資源,那麼其餘全部阻塞在這個臨界區的線程都不能正常工做。

    非阻塞與值相反,他強調沒有一i個縣城能夠妨礙其餘線程執行,全部的線程都會嘗試不斷向前執行,接下來會詳細描述。

死鎖、飢餓和活鎖

    死鎖、飢餓和活鎖都屬於多線程的活躍性問題。若是發生以上幾種狀況,那麼線程可能很難在繼續往下執行了。

死鎖

    死鎖是最糟糕的一種狀況,指兩個或兩個以上的進程在執行過程當中,因爭奪資源而形成的一種互相等待的現象,若無外力做用,它們都將沒法推動下去。此時稱系統處於死鎖狀態或系統產生了死鎖,這些永遠在互相等待的進程稱爲死鎖進程。因爲資源佔用是互斥的,當某個進程提出申請資源後,使得有關進程在無外力協助下,永遠分配不到必需的資源而沒法繼續運行,這就產生了一種特殊現象死鎖。

    雖然進程在運行過程當中,可能發生死鎖,但死鎖的發生也必須具有必定的條件,死鎖的發生必須具有如下四個必要條件。

  • 互斥條件:指進程對所分配到的資源進行排它性使用,即在一段時間內某資源只由一個進程佔用。若是此時還有其它進程請求資源,則請求者只能等待,直至佔有資源的進程用畢釋放。
  • 請求和保持條件:指進程已經保持至少一個資源,但又提出了新的資源請求,而該資源已被其它進程佔有,此時請求進程阻塞,但又對本身已得到的其它資源保持不放。
  • 不剝奪條件:指進程已得到的資源,在未使用完以前,不能被剝奪,只能在使用完時由本身釋放。
  • 環路等待條件:指在發生死鎖時,必然存在一個進程——資源的環形鏈,即進程集合{P0,P1,P2,···,Pn}中的P0正在等待一個P1佔用的資源;P1正在等待P2佔用的資源,……,Pn正在等待已被P0佔用的資源。

飢餓

    飢餓是指某一個或多個線程由於種種緣由沒法得到所須要的資源,致使一直沒法執行。好比它的線程優先級可能過低,而高優先級線程不斷搶佔它所需的資源,致使低優先級線程沒法工做。還有一種狀況就是某一線程一直站着關鍵資源不放,致使其餘須要這個資源的縣城沒法正常執行,這種狀況也是飢餓的一種。與死鎖相比,飢餓仍是有可能在將來的一段事件內解決的。

活鎖

    活鎖指事物1可使用資源,但它讓其餘事物先使用資源;事物2可使用資源,但它也讓其餘事物先使用資源,因而二者一直謙讓,都沒法使用資源。 

併發級別

    因爲臨界區的存在,多線程之間的併發必須受到控制。根據控制併發的策略,咱們能夠把併發的級別進行分類,大體上能夠分爲阻塞、無飢餓、無障礙、死鎖、無等待幾種。

阻塞

一個縣城是阻塞的,那麼在其餘線程釋放以前,當前線程沒法繼續工做。當咱們使用synchronized關鍵字,或者重入鎖時咱們獲得的就是阻塞的線程。

    不管是synchronized或者重入鎖,都會試圖在執行後續代碼前,獲得臨界區的鎖,若是得不到,線程就會被掛起等待,直到佔用了所需資源爲止。

無飢餓

    若是線程以前是有優先級的,那麼線程調度的時候老是會傾向於高優先級線程。也就是說同一資源的分配是不公平的。對於非公平的鎖來講,系統容許高優先級的縣城插隊。這樣有可能致使低優先級線程產生飢餓。但若是鎖是公平的,知足先來後到的原則,那麼飢餓就不會產生,無論新來的線程優先級多高,要想得到資源,就必須乖乖排隊。那麼全部的線程都有機會執行。

無障礙

    無障礙是一種最弱的非阻塞調度。兩個線程若是是無障礙的執行,那麼他們不會由於臨界區的問題致使一方被掛起。你們均可以大搖大擺進入臨界區工做。那麼若是你們都修改了共享數據怎麼辦呢?對於無障礙的線程來講,一旦出現這種狀況,當前線程就會當即對修改的數據進行回滾,確保數據安全。但若是沒有數據競爭發生,那麼線程就能夠順利完成本身的工做,走出臨界區。 
    若是阻塞控制的方式比喻成悲觀策略。也就是說系統認爲兩個線程之間頗有可能發生不幸的衝突,所以,保護共享數據爲第一優先級。相對來講,非阻塞的調度就是一種樂觀策略,他認爲多線程之間頗有可能不會發生衝突,或者說這種機率不大,可是一旦檢測到衝突,就應該回滾。 
    從這個策略來看,無障礙的多線程程序不必定能順利執行。由於當臨界區的字眼存在嚴重的衝突時,全部線程可能都進行回滾操做,致使沒有一個線程能夠走出臨界區。因此咱們但願在這一堆線程中,至少能夠有一個線程能夠在有限時間內完成本身的操做,至少這能夠保證系統不會再臨界區進行無線等待。 
    一種可行的無障礙實現能夠依賴一個「一致性標記」來實現。線程在操做以前,先讀取並保持這個標記,在操做完後,再次讀取,檢查這個標記是否被修改過,若是先後一致,則說明資源訪問沒有衝突。若是不一致,則說明資源可能在操做過程當中與其餘寫線程衝突,須要重試操做。任何對保護資源修改以前,都必須更新這個一致性標記,表示數據不安全。

無鎖

    無鎖的並行都是無障礙的。在無鎖的狀況下,全部的線程都能嘗試對臨界區的資源進行訪問,但不一樣的是,無鎖的併發保證必然有一個線程可以在有限步內完成操做離開臨界區。 
    在無鎖的調度中,一個典型的特色是可能會包含一個無窮循環。在這個循環中線性不斷嘗試修改共享數據。若是沒有衝突,修改爲功,那麼線程退出,不然嘗試從新修改。但不管如何,無鎖的並行總能保證有一個線程能夠勝出,不至於全軍覆沒。至於臨界區中競爭失敗的線程,則不斷重試。若是運氣很差,老是不成功,則會出現相似飢餓的現象,線程會中止不前。

無等待

    無鎖是要求至少有一個線程在有限步內完成操做,而無等待則是在無鎖的基礎之上進一步擴展。他要求全部線程都必須在有限步內完成操做。這樣就不會引發飢餓問題。若是限制這個步驟上限,還能夠分爲有界無等待和線程無關的無等待幾種,它們之間的區別只是對循環次數的限制不一樣。 
    一種典型的無等待結構是RCU(read-copy-update)。它的基本思想是,對數據的讀能夠不加控制,所以全部讀線程都是無等待的,它們既不會被鎖定等待也不會引發任何衝突。但在寫數據時,先取得原始數據的副本,接着只修改副本數據,修改完後,在合適的時機回寫數據。

線程狀態

    要獲取狀態能夠經過線程(Thread)的getState()來獲取狀態的值。例如,獲取當前線程的狀態就可使用Thread.currentThrad().getState()來取值。該方法返回的類型是一個枚舉類型,包含了new、runnable、blocked、waiting、timed_waiting、terminated這些值。

public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}
  • new:狀態表示剛剛建立的線程,這種線程尚未執行。等到線程的start()方法調用時,才表示線程開始執行。當線程執行時,處於runnable狀態.
  • runnable:線程所需的一切資源都已經轉杯好了。若是線程在執行中遇到了synchronized同步塊,就會進入blocked阻塞狀態,
  • blocked:此時的狀態爲線程會暫停執行,直到得到請求的鎖。
  • waiting:進入一個無時間限制的等待。等待特殊事件,如經過wait()方法等待的線程在等待notify方法,而經過join()方法等待的線程則會等待目標線程終止。一旦等到了指望的事件,線程就會再次執行,進入runnable狀態。
  • timed_waiting:進入一個有時間限制的等待。等待特殊事件,如經過wait()方法等待的線程在等待notify方法,而經過join()方法等待的線程則會等待目標線程終止。一旦等到了指望的事件,線程就會再次執行,進入runnable狀態。
  • terminated:當線程執行完畢後,則進入terminated狀態,表示結束。

這裏寫圖片描述

相關文章
相關標籤/搜索