java高併發編程(一)——瞭解併發編程

併發編程筆記和備忘

記錄學習併發編程過程中的的筆記和理解,有些實例出自自我總結和書本知識,有錯誤的地方希望前輩們指出。萬分感謝。

一、併發編程的幾個重要概念

(1)“同步”(Synchronous)和“異步”(Asynchronous)
  “同步”方法的調用,調用者必須等到方法調用返回後才能繼續後續的操作。‘’異步”方法調用之後立即返回,調用者可以繼續後續的操作。兩者的差別可以用生活中的實體店購物(對應同步)和網購(對應異步)來體現。實體店購物,在我們對店家說想買某件商品比如衣服之後,我們就一直在店裏等待老闆拿出衣服,包裝好,然後付款,再回家這一整個過程。而網購在選好商品下單之後,我們就可以自由的處理其他事,只要等快遞上門,簽收就行了。
(2)“併發”(Concurrency) 和 “並行”(Parallelism)
  “併發”和“並行”都表示的是多個任務一起執行。然而“併發”側重的是多個任務交替進行,這些任務之間可能還是串行的,並非真正意義上的同時執行。“並行”表示的是多個任務真正的同時進行。兩者好比寫作業時交頭接耳。“併發”同學一邊寫作業一邊和同桌聊天,但是聊的很投入,導致在講話的時候就把筆放下了,在寫作業的時候卻沒有說話。“並行”同學一心二用,右手奮筆疾書,嘴裏同時還滔滔不絕。課後紀錄委員報告老師的時候說的是這兩個同學上課一邊寫作業一邊聊天。
(3)“臨界區”
  臨界區表示的是可以被多個線程使用的公共資源,然而每次只有一個線程可以得到它的使用權。臨界區一旦被佔用,其他線程想要獲取它的使用權就只能等待。
(4)“阻塞”(Blocking)和“非阻塞”(Non-Blocking)
  阻塞即請求之後,數據未準備好,不立即返回,需要等待。非阻塞即數據未準備好,但是立即返回。
(5)“死鎖”(Deadlock)、“飢餓”(Starvation)和“活鎖”(Livelock)
  死鎖指的是多個線程期望獲取對方的鎖,但是自身又佔用對方需要的鎖,若沒有外力的干預則這些線程一直處於等待狀態,形成死鎖。死鎖對應的典型事例就是四輛行駛方向不同的汽車,各自堵住了道路如下圖所示在這裏插入圖片描述
  飢餓指的是線程一直獲取不到期望獲取的資源。由於某些線程的高優先級導致低優先級的線程一直獲取不到所需資源即造成了飢餓。好比一家極受歡迎的麪包店,顧客只能通過爭搶才能買到麪包,那麼強壯的人一直可以買到麪包,但是瘦弱的人就一直買不到了。
  活鎖指的是線程拿到了資源,卻都釋放不執行,導致了資源在多個線程之間不停跳動,但是又沒有執行。好比哥哥弟弟喫蘋果,哥哥覺得弟弟小拿了蘋果卻想要給弟弟喫,弟弟覺得哥哥是長輩所以要哥哥喫,所以弟弟拿到哥哥給的蘋果之後,又馬上給了哥哥,這樣,蘋果在哥哥弟弟手裏不斷的給來給去,卻一個人都沒有喫蘋果。

二、併發級別

(1)阻塞(Blocking)
  一個線程阻塞的,在其他資源沒有釋放資源的之前,都無法繼續執行
(2)無飢餓(Starvation-Free)
  由於線程具有優先級差別,資源就會出現分配不均的現象。對於非公平的鎖而言,系統允許其插隊,這樣就有可能導致低優先級的線程產生飢餓。如果鎖是公平的,高優先級的線程也要按先來後到的順序進行,相當於不管你身份多NB,就是不許插隊的意思。那麼所有線程就都有機會執行
(3)無障礙(Obstruction-Free)
  無障礙的線程執行時,不會因爲臨界區的問題而導致另一方被掛起,大家一起對共享數據進行操作,但是同時操作會導致一系列問題,比如兩線程都對其進行修改,那數據有可能改的面目全非。一旦發現線程之間的衝突,線程自身會立即回滾自己的操作,保證數據安全。然而如果兩個線程所操作的數據並沒有發生衝突,那麼線程會各自完成自己的工作,釋放資源。然而當線程數量多,產生衝突過多的時候,沒有一個線程可以走出臨界區,所有的線程可能都在不斷的回滾自己的操作,這種情況明顯是不符合預期的。
  一種可行的無障礙的實現方式是在共享資源中設置一個一致性標記。所有的線程修改數據的時候都要事先修改這個一致性標記。線程工作時先讀取並保存它,在操作完成後再次讀取,如果兩者一致,則資源沒有衝突,如果不一致,則說明可能與其他線程存在衝突。
(4)無鎖(Lock-Free)
  無鎖的並行都是無障礙的。但是無鎖的併發保證必然有一個線程能在有限的工作步驟內執行完成。
(5)無等待
  無等待要求在無鎖的基礎上,所有的線程都在有限的步驟之內完成,這樣就不會引起飢餓問題。一種典型的無等待結構就是RCU(Read-Copy-Update)。對數據進行讀取的時候不加以控制,所有的線程都是無等待的。但是在寫數據的時候,先獲取數據的副本,在副本上修改,在適當的時機回寫數據。

三、三大特性

(1)原子性
  原子性是指一個操作是不可中斷的,即多個線程一起執行的時候,一個操作一旦執行,就不會被其他線程干擾。比如,兩個線程同時對int i 賦值,程序結束後i的值要麼是1要麼是-1。兩線程之間是沒有干擾的。
(2)可見性
  可見性指的是當一個線程修改了共享資源的數據之後,其他的線程能否立刻知道這個修改。在並行的程序中,可見性問題是存在的。比如當cpu1和cpu2各有一個線程同時對一個int i進行操作時,cpu1將i進行了優化,並存在緩存中或者寄存器中,cpu2的線程對i進行修改,那麼cpu1可能並不能馬上能得知這一修改,讀取到的依舊是舊值。
(3)有序性
  當併發程序執行時,程序的執行順序可能並不是按照順序,從前往後執行的,寫在前面的程序可能在之後執行。在java裏可以通過volatile來保證一定的有序性,另外也可以通過synchroized和lock來保證有序性。synchroized和lock是保證每個時刻是隻有一個線程執行同步代碼,相當於是讓線程順序執行代碼從而保證有序性(單線程看有序,不會改變最終的結果,但是多線程情況下就不一定了)。Java具備一些先天的有序性(不需要任何手段就能保證有序性),即happens-before原則(先行發生原則)。如果兩個操作的執行次序無法從happens-before原則推導出來,那麼它們就不能保證有序性。虛擬機可以隨意的對它們進行重排序。

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