java之線程那些事兒(二)

      編寫線程安全的代碼,核心就在於對程序狀態訪問操作進行管理,特別是對共享(shared)和可變的(mutable)狀態的訪問;
      一般來說,對象的狀態是指對象域中的實時數據(如實例或者靜態域中的數據),數據發生了改變,我們就說對象的狀態發生了改變,就如今天的你不同於昨日的你;
  注意,對象的狀態可能還包括其以來對象的域;比如,我麼你所熟知的容器(HashMap,ArrayList),容器的狀態不僅僅存儲在其本身,還包括存儲在其中的對象中包含
  的任何可能影響其外部可見性的數據;
      共享--同時會有多個線程對其進行訪問,可變--變量的值在其生命週期內會發生變化,單獨來看,沒有問題,可二者結合起來,就給線程安全造成了嚴重的破壞性,
  所以討論線程安全性,重點在於如何防止在數據上發生不受控制的併發訪問,也就是如何保證所有人都能正確的訪問和操作數據;
      另外,一個對象是否需要線程安全性,取決於它是否會被多個線程訪問,需要注意的是我們所用的一些框架在調用應用程序代碼的時候,將併發性引入到了程序中,
  因此對線程安全的需求將在程序中蔓延開來;
      一些程序剛開始應用時,訪問量小,程序可以正常的執行,但是一旦訪問增加時,就是我們所說的高併發訪問,程序就可能出現意想不到的錯誤,而這個時間可能是
  幾個月,甚至是幾年,運氣好的話可能永遠不會出現(你的程序可能沒人用了吧)~
      當出現併發訪問可變的狀態變量時,如果沒有特殊處理,從前面的內容我們可以通過3種方式來修復:
      1.取消該狀態的共享;
      2.將該變量設置爲不可變;
      3.訪問時同步;
      顯然,爲了適應業務需求,1和2我們無法實現,所以我們只能採用方法3;我們必須採用同步機制來協同這些線程對變量的訪問與操作,這裏就需要提到synchronized
  關鍵字,它提供了一種獨特的加鎖方式,但是“同步”還這是術語還包括volatile(可見),explicit lock(顯式鎖)以及atomic(原子性)類型的變量(至於這三種的
  區別,我們稍後再細細道來);
      如果在設計類的時候,並沒有考慮併發的情況(我相信大部分新手都是如此),那麼採用上述方法可能需要對程序進行重大修改,因爲java並沒有強制要求將狀態都
  封裝在類中,所以要找出該變量在哪些位置出現了併發訪問,尤其是一些大型工程,更是複雜,修復起來是知易行難,所以從一開始就設計一個線程安全的類會好的多,這
  就要求我們充分發揮java的封裝性(封裝越好,越安全);當然有時候一些抽象和封裝會降低程序的性能,但是與讓代碼正確的運行起來這個目的相比,性能就只能先靠邊
  了;所以併發編程的守則就是:先讓代碼正確的運行,然後再提高性能,而且只有當性能測試結果和需求要求你必須提高性能以及某些優化缺失能提高性能時,才進行主
  動優化;
      那麼什麼是線程安全性,簡單來說,它是一個在代碼上應用的術語,只與狀態相關,對象封裝這些狀態的整個代碼,可以是單個對象,也可以是對象集合,甚至是整個
  程序;因此我們可以以java的基本單元類爲標準來闡述安全性:
      當多線程訪問某個類時,這個類的行爲與其規範(通過不變形條件來約束狀態--invariant,通過後驗條件來描述操作結果--postcondition)始終一致,那麼就稱
  這個類是線程安全的,這個訪問操作就具有線程安全性;而且只有討論類的狀態時嗎,安全性纔有意義,無狀態的對象一定是線程安全的;
      具體的來說,就是噹噹問一個可變狀態時,如果對其進行的操作不是原子性操作,則會由於如指令重排序等原因,造成指令的執行順序出現多樣化,結果每個線程執行
  的狀態同一時刻處於不同操作,結果將導致嚴重的數據完整性問題,這種現象被稱爲race condition--競態條件--計算的正確性完全取決於多個線程的交替執行;而最常
  見的類型就是--先檢查後執行,本質就是基於一種可能失效的結果來做出響應,結果當然可能不正確;另外還有一種就是數據競爭--data race,在訪問非final的共享域
  時沒有同步,就會出現數據競爭(讀取由之前的另一個線程寫入的變量);爲了確保線程安全性,之前的哪些操作必須是原子性的,所以我們組合操作稱之爲符合操作;然
  後通過加鎖來確保其線程安全性(在操作完成之前,阻止其他線程進行操作);
      java提供了一種內置鎖(同步代碼塊--synchronized block),synchronized方法以Class對象爲內置鎖,同時只有一個線程可以獲取該鎖,當然同一個線程可以
  多次獲取該鎖,我們稱之爲“重入”,JVM會記錄下鎖得持有者和計數值,只有當計數值爲0的時候纔會釋放;所以一定不要在執行時間長的計算或者無法快速完成的操作中加
  鎖(比如I/O),這樣會長時間阻塞程序;所以用鎖需謹慎!
      

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