線程同步的方法(二)

線程同步

       當多個線程訪問同一個數據時,容易出現線程安全問題。需要讓線程同步,保證數據安全。即當兩個或兩個以上線程訪問同一資源時,需要某種方式來確保資源在某一時刻只被一個線程使用。

線程同步的實現:

1. 使用synchronized關鍵字(同步方法或代碼塊)

    java的每個對象都有一個內置鎖,內置鎖會保護整個方法。在調用該方法前,需要獲得內置鎖,否則就處於阻塞狀態。

  注:

     synchronized關鍵字也可以修飾靜態方法,此時如果調用該靜態方法,將會鎖住整個類。 同步是一種高開銷的操作,因此應該儘量減少同步的內容。通常沒必要同步整個方法,使用synchronized同步關鍵代碼塊即可。

同步方法:給一個方法增加synchronized修飾符後就成爲了同步方法,這個方法可以是靜態方法和非靜態方法,但不能爲抽象方法。同步方法是對這個方法塊裏的代碼進行同步,這種情況下鎖定的對象就是同步方法所屬的主體對象自身。若這個方法爲靜態同步方法鎖定的是這個類對應的java.lang.Class類型的對象。

同步代碼塊:通過鎖定一個指定對象,來對同步代碼塊中包含的代碼進行同步。

synchronized用於保護共享數據,一定要分清哪些數據是共享數據。

2. Lock

      JDK1.5後新增功能,與採用synchronized相比,lock可提供多種鎖方案,更靈活。

       java.util.concurrent.lock中的Lock框架是鎖定的一個抽象,它允許把鎖定的實現作爲Java類,而不是作爲語言的特性來實現。這就爲Lock的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。

       ReentrantLock類實現了Lock,它擁有與synchronized相同的併發性和內存語義,但是添加了類似鎖投票、

定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。

注意:如果同步代碼有異常,要將unlock()寫入finally語句塊。

Lock和synchronized的區別:

      1.   synchronized 和 lock 的用法區別
        synchronized(隱式鎖):在需要同步的對象中加入此控制,synchronized 可以加在方法上,也可以加在特定代碼塊中,括號中表示需要鎖的對象。
       lock(顯示鎖):需要顯示指定起始位置和終止位置。一般使用 ReentrantLock 類做爲鎖,多個線程中必須要使用一個
 ReentrantLock 類做爲對象才能保證鎖的生效。且在加鎖和解鎖處需要通過 lock() 和 unlock() 顯示指出。所以一般會在finally 塊中寫 unlock() 以防死鎖。

     Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖

     2. lock是一個接口,而synchronized是java中的關鍵字,synchronized是內置的語言實現。

     3. synchronized在發生異常時,會自動釋放線程佔有的鎖,不會導致死鎖現象的發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;

     4. Lock可以讓等待鎖的線程響應中斷,而synchronized卻不行,使用synchronized時,等待的線程會一直等待下去,不能夠響應中斷;通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。

     5.  synchronized 和 lock 性能區別 

      synchronized 是託管給 JVM 執行的,而 lock 是 Java 寫的控制鎖的代碼。使用Lock鎖,JVM將花費較少的時間來調度線程,性能更好。並且具有更好的擴展性(提供更多的子類)。在 JDK 1.5 中,synchronize 是性能低效的。因爲這是一個重量級操作,需要調用操作接口,導致有可能加鎖消耗的系統時間比加鎖以外的操作還多。相比之下使用 Java 提供的 Lock 對象,性能更高一些。但是到了 JDK 1.6,發生了變化。synchronized在語義上很清晰,可以進行很多優化,有適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在 JDK 1.6 上 synchronized的性能並不比 Lock 差。

在性能上來說,如果競爭資源不激烈,兩者的性能是差不多的,而當競爭資源非常激烈時(即有大量線程同時競爭),此時Lock的性能要遠遠優於synchronized。所以說,在具體使用時要根據適當情況選擇。

    6. synchronized 和 lock 機制區別
synchronized 原始採用的是 CPU 悲觀鎖機制,即線程獲得的是獨佔鎖。獨佔鎖意味着其他線程只能依靠阻塞來等待線程釋放鎖。Lock 用的是樂觀鎖方式。所謂樂觀鎖就是,每次不加鎖而是假設沒有衝突而去完成某項操作,如果因爲衝突失敗就重試,直到成功爲止。樂觀鎖實現的機制就是 CAS 操作(Compare and Swap)。

3. volatile實現線程同步

  volatile關鍵字爲域變量的訪問提供了一種免鎖機制。需要在同步的變量前面加時volatile修飾,即可實現線程同步。

     1.多線程中的非同步問題主要出現在對域的讀寫上,如果讓域自身避免這個問題,則就不需要修改操作該域的方法。

     2.volatile不能保證原子操作,因此volatile不能代替synchronized

     3.每次要線程要訪問volatile修飾的變量時都是從內存中讀取,而不是從緩存當中讀取,因此每個線程訪問到的變量值都是一樣的。這樣就保證了同步。

使用場景:

      1)對變量的寫操作不依賴當前值。

      2)該變量沒有包含在具有其他變量的不變式中。也就是變量取值時值的內容獨立於程序其他部分。

4. 線程同步的方法:

     wait():使一個線程處於等待狀態,並且釋放所持有的對象的lock。

     sleep():使一個正在運行的線程處於睡眠狀態,是一個靜態方法,調用此方法要捕捉 InterruptedException異常。

     notify():喚醒一個處於等待狀態的線程,注意的是在調用此方法的時候,並不能確切的 喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。

     notityAll ():喚醒所有處入等待狀態的線程,注意並不是給所有喚醒線程一個對象的鎖, 而是讓它們競爭。

線程同步的優缺點:

       優點:解決了線程安全問題。

       缺點:性能下降,會帶來死鎖。

有關死鎖的認知,參考博客:https://blog.csdn.net/duan196_118/article/details/104653053

如有不足,歡迎留言指正。望不吝賜教。。。

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