1. 程式人生 > >《JAVA多線程編程核心技術》 筆記:第二章:對象及變量的並發訪問

《JAVA多線程編程核心技術》 筆記:第二章:對象及變量的並發訪問

問題 內部類 nds safety string line 基本概念 子類 標記

一、基本概念
1、安全的變量和不安全的變量
2、臟讀的理解
3、鎖重入:
4、鎖釋放
5、死循環:
二、synchronized 的理解:
三、synchronized 同步方法
3.1 同步方法不具有繼承性。
3.2 同步方法的弊端:
四、synchronized 同步代碼塊
4.1 synchronized(this)同步代碼塊
4.1.1 實現:
4.1.2 註意:
4.2 synchronized(x):將任意對象作為對象監視器:
4.3 synchronized、synchronized(this)和synchronized(非 this 對象)的對比
4.3.1 區別
4.3.2 細化驗證3個結論:
4.4 靜態同步synchronized方法和synchronized(class)代碼塊
4.4.1 用到static 靜態方法非static 靜態方法上的區別:
4.4.2 Class鎖
4.4.3 synchronized(class)代碼塊和synchronized static 方法的相同點:
五、volatile關鍵字的使用
5.2、synchronized 和 volatile 的比較(可見性和原子性):
六、其他註意
6.1 String對象作為鎖的註意事項
6.2 內部類和靜態內部類的實例化的兩種不同語法:
6.3 鎖對象的改變
6.4 讀寫操作和原子類說明:
七、END!

一、基本概念

1、安全的變量和不安全的變量

1.1 安全的:

  1. 方法內的變量為線程安全。因為方法內部變量私有。
    為什麽?(局部變量存儲在線程自己的棧中。也就是說,局部變量永遠也不會被多個線程共享。參考:線程安全與共享資源 | 並發編程網 – ifeve.com http://ifeve.com/thread-safety/)
  2. 多個對象多個鎖:即synchronized 獲取的是對象鎖。例子中是因為a在休眠,a和b持有的是不同的對象鎖,互不影響。所以b自己先執行完了。對比之前的例子:a和b用的是同一個對象,即同一個鎖,所以必須一個先執行完,才能執行另一個。

1.2 不安全的

實例變量(即對象內)非線程安全。可能會被覆蓋。

2、臟讀的理解

可參考第3點的總結。

3、鎖重入:

  • 自己可以再次獲取自己的內部鎖。如果沒有鎖重入,就會造成死鎖。
  • 鎖重入支持父子繼承:即子類可以調用父類的同步方法(synchronized )。實際可這麽理解:父類的方法實際就是子類的方法,子類調用的還是自己的同步方法(只是書寫方式比較簡單)。自己對自己鎖重入完全沒問題。

4、鎖釋放

出現異常後,鎖會自動釋放。

5、死循環:

  • 只有一個線程,如果遇到死循環,就會一直死下去,無法改變;
  • 如果兩個線程可以控制同樣的變量,那麽程序的執行就可以被改變了。

二、synchronized 的理解:

  1. synchronized 獲取的是對象鎖(不同對象不同鎖,同一對象才是一個鎖)。
  2. synchronized 聲明的方法一定排隊運行。另外,只有共享資源才需要同步,非共享資源根本沒有同步的必要。
  3. 對於 synchronized 方法和 非 synchronized 方法
    • 當線程A持有該對象object的Lock鎖時,線程B可以異步訪問對象object中的非synchronized 方法;
    • 當線程A持有該對象object的Lock鎖時,線程B必須同步訪問對象object中的synchronized 方法;

  • 雖然synchronized 獲取的是對象鎖,鎖只對對象中標有 synchronized 的所有代碼其作用,保證其線程安全。對其他代碼不保證線程安全,如果其他代碼線程不安全,可能會出現臟讀。
  • 只有線程獲取到鎖之後,才能對 synchronized 標記的代碼進行操作。

三、synchronized 同步方法

3.1 同步方法不具有繼承性。

即子類有一個和父類同名的方法:父類中的方法有synchronized ,子類中的方法沒有synchronized ,那麽子類中的方法不是同步的。這個也很好理解,因為子類覆寫同名方法,相當於覆蓋了父類的方法,自己沒寫自然沒有。

3.2 同步方法的弊端:

耗時較長,因為當一個線程持有鎖時,其他線程只能等待。

四、synchronized 同步代碼塊

4.1 synchronized(this)同步代碼塊

4.1.1 實現:

synchronized(this){
    //相關代碼
}

4.1.2 註意:

當一個線程訪問object的一個synchronized (this)時,其他線程對同一個object中所有其他synchronized (this)同步代碼塊的訪問將被阻塞,這說明synchronized 使用的“對象監視器”是一個。

可以這麽理解:

對於 synchronized 鎖的是一個對象,那麽如果線程要執行相關synchronized 代碼,都必須先得到對應的對象鎖。不管synchronized 限制的是方法還是代碼塊。

4.2 synchronized(x):將任意對象作為對象監視器:

具體理解看原文吧,沒什麽好理解的。

4.3 synchronized、synchronized(this)和synchronized(非 this 對象)的對比

4.3.1 區別

  1. synchronized同步方法(獲取的是this的對象鎖)
    • 對其他synchronized 同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態;
    • 同一時間只有一個線程可以執行synchronized同步方法中的代碼;
  2. synchronized(this)同步代碼塊(獲取的是this的對象鎖)
    • 對其他synchronized 同步方法或synchronized(this)同步代碼塊調用呈阻塞狀態;
    • 同一時間只有一個線程可以執行synchronized(this)同步代碼塊中的代碼;
  3. synchronized(非 this 對象)同步代碼塊(獲取的是非 this 對象的對象鎖)
    • 持有“對象監視器”為同一個對象(即該非 this 對象的對象鎖)的前提下:同一時間只有一個線程可以執行synchronized((非 this 對象)同步代碼塊中的代碼;

總結:

this的對象鎖和非 this 對象的對象鎖不是同一個鎖,所以無法保證線程安全。

  • 如果非 this 對象表明的是同一個鎖(即this鎖):那麽和之前synchronized沒有區別
  • 如果非 this 對象表明的不是同一個鎖(每個線程自己的遍歷):那麽就是異步,會線程不安全。

4.3.2 細化驗證3個結論:

  1. 當多個線程同時執行synchronized(x){}同步代碼塊時呈同步效果;
  2. 當其他線程執行執行x對象中synchronized同步方法時呈同步效果;
  3. 當其他線程執行執行x對象方面裏面的synchronized(this)代碼塊時呈同步效果;

總結:上述3點完全無問題,線程安全的意義就是:同一時刻只能有一個線程獲取該對象的對象鎖,只要對象是同一個,那麽對象鎖就是同一個。那麽肯定會保證同步效果。

4.4 靜態同步synchronized方法和synchronized(class)代碼塊

4.4.1 用到static 靜態方法非static 靜態方法上的區別:

  • synchronized使用到static 靜態方法上,那麽是對當前的*.java文件對應的Class類進行持鎖(即Class鎖)。
  • synchronized使用到非static 靜態方法上,是給對象加鎖。

4.4.2 Class鎖

Class鎖對類的所有對象實例起作用。它是在對象上一層級的一個鎖。

如果有一個Class鎖,該Class鎖有N個對象,兩N個對象由N個線程分別執行,對於synchronized(class)代碼塊依舊是同步訪問。

4.4.3 synchronized(class)代碼塊和synchronized static 方法的相同點:

實際作用一致。

五、volatile關鍵字的使用

## 5.1、關鍵字volatile的作用

5.1.1 作用:

強制從公共堆棧中取得變量的值,而不是從線程私有數據棧中取得變量的值。

5.1.2 作用的理解:為什麽會用這種作用?

一個對象同時傳給兩個線程,看著是一個對象,但實際上,每個線程都保存有自己的一份數據。各個線程對數據的更改都是基於自己的副本進行操作。

不會立即將改變傳遞到公有變量中,所以可能會導致多個線程無法控制線程同步的情況。

5.1.3 註意代碼中的例子:

兩個例子,一個是extends Thread,一個是implements Runnable,這兩種方式有什麽不一樣麽?

在測試中:

implements Runnable 的對象中的變量,好像就是在公用堆棧中,所以更改可以立即生效。

但是: extends Thread 好像是在自己的私有堆棧中,只有使用 volatile 才會使其生效。

所以,建議多使用implements

  • 非多繼承的情況下:implements和extends 沒區別;
  • 多繼承情況下:使用implements,防止出錯。

5.1.4 例子總結

64位-server模式:啟動線程RunThread時,設置isRunning = true,存在於公共堆棧,以及線程的私有堆棧中;

64位-server模式為了線程運行效率,線程一直從私有堆棧中取得isRunning = true。

而:thread.setRunning(false); 更新的是公共堆棧中的isRunning = false。所以代碼一直是死循環。

主要原因:公用堆棧中的值和私有堆棧中的值不同步;

解決方法,在isRunning 前加上volatile關鍵字(強制從公共堆棧中取值)

5.2、synchronized 和 volatile 的比較(可見性和原子性):

volatile 關鍵字
(解決變量在多個線程之間的可見性)
synchronized 關鍵字
(解決多個線程之間的訪問資源的同步性)
線程同步的輕量實現,比 synchronized 好; 比volatile差一點
只能修飾變量 可修飾方法、代碼塊和變量
不會阻塞 會阻塞
保證數據的可見性(讀階段),不保證原子性(寫階段)
(即多個線程同時更改同一變量,就會有問題);
保證原子性(寫階段),間接保證可見性(讀階段)
(因為會將私有內存和公有內存中中的數據同步);

其他說明:

synchronized 代碼塊有volatile 同步的功能:

  • synchronized可以使多個線程訪問同一個資源具有同步性(即同一時刻,只有一個線程可以執行synchronized保護的代碼;);
  • synchronized還能將線程工作內存中的私有變量和公共內存中的變量同步(可以解決一個線程看到對象處於不一致的狀態,還可以保證進入同步方法或同步代碼塊的每個線程,都看到由同一個鎖保護之前所有的修改效果;)

六、其他註意

6.1 String對象作為鎖的註意事項

註意:如果使用String對象作為鎖,需註意當String一致時,他們其實是同一個對象,不像對象那樣,即使裏面的String相同,也不是同一個對象。

"aa" == "aa" //true
new String("a") == new String("a") //false

如果不註意可能會導致鎖的有問題。

6.2 內部類和靜態內部類的實例化的兩種不同語法:

非靜態內部類:  外部類.new 內部類名
靜態內部類: 直接 new 類名即可,和普通的類新建對象沒有什麽區別。

6.3 鎖對象的改變

這個例子說明了兩個知識點:

  • 鎖的爭搶只在線程開始執行的一瞬間,後續對於該鎖的爭搶是不會變的。即使在代碼中動態改變了鎖對應的對象。只要代碼沒有執行完,那麽所有相關的鎖都是一樣的。

對象不變,即使對象的屬性被改變,那麽對該對象持有的鎖並不會變。

6.4 讀寫操作和原子類說明:

1、讀寫操作一般分為3階段:讀→用→寫。讀和寫各為原子操作。但讀寫在一起並不為原子操作。

2、可以使用原子類進行 i++操作:

註意:原子類本身操作安全,但是如果代碼執行順序不安全,從線程整體來看,還是不安全的。

七、END!

《JAVA多線程編程核心技術》 筆記:第二章:對象及變量的並發訪問