Java中syncrhoized ,reentrantLock ,Atomic ,Lock ,ThreadLocal ,transient ,volatile,物件鎖和類鎖

desaco發表於2016-05-20

 記憶體的原子性、可見性 & 有序性;
 volatile保證可見性 & 有序性,不保證原子性。

-- 工作記憶體與主記憶體怎麼進行互動?虛擬機器定義了8種原子操作
1.lock(鎖定主記憶體的變數,使其被某一執行緒獨佔),
2.unlock(同理),
3.read(把一個主記憶體的變數傳遞到工作記憶體中,以便load),
4.load(將從主記憶體傳遞的值傳遞到工作記憶體的變數副本中),
5.store(將工作記憶體中變數副本傳遞到主記憶體中去,以便write),
6.write(將工作記憶體傳遞過來的值賦到主記憶體中變數),
7.use(將工作記憶體的值傳遞給執行引擎),
8.assign(將執行引擎的值傳遞到工作記憶體),這8中操作可以用來確定你的訪問是否安全。

> 物件鎖和類鎖

- java的物件鎖和類鎖:java的物件鎖和類鎖在鎖的概念上基本上和內建鎖是一致的,但是,兩個鎖實際是有很大的區別的,物件鎖是用於物件例項方法,或者一個物件例項上的,類鎖是用於類的靜態方法或者一個類的class物件上的。我們知道,類的物件例項可以有很多個,但是每個類只有一個class物件,所以不同物件例項的物件鎖是互不干擾的,但是每個類只有一個類鎖。但是有一點必須注意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定例項方法和靜態方法的區別的

- synchronized 
 在修飾程式碼塊的時候需要一個reference物件作為鎖的物件. 
 在修飾方法的時候預設是當前物件作為鎖的物件. 
 在修飾類時候預設是當前類的Class物件作為鎖的物件.

> syncrhoized ,reentrantLock,Atomic ,Lock,ThreadLocal,transient,volatile各自特點和比對:
 1. synchronized: 
 在資源競爭不是很激烈的情況下,偶爾會有同步的情形下,synchronized是很合適的。原因在於,編譯程式通常會盡可能的進行優化synchronize,另外可讀性非常好,不管用沒用過5.0多執行緒包的程式設計師都能理解。
 synchronized在鎖定時如果方法塊丟擲異常,JVM 會自動將鎖釋放掉,不會因為出了異常沒有釋放鎖造成執行緒死鎖。但是 Lock 的話就享受不到 JVM 帶來自動的功能,出現異常時必須在 finally 將鎖釋放掉,否則將會引起死鎖。
 synchronized和Lock保證了物件、變數的原子性和可見性,volatile保證變數的可見性。原子性的指令 CAS (compare and swap)。

深入理解Java併發之synchronized實現原理- http://blog.csdn.net/javazejian/article/details/72828483

 -- synchronized實現同步的基礎:
①:對於普通方法,鎖是當前例項物件。
②:對於靜態同步方法,鎖是class物件。
③:同步方法塊,鎖是synchronized後面括號裡的物件。

 synchronized在靜態方法上表示呼叫前要獲得類的鎖,而在非靜態方法上表示呼叫此方法前要獲得物件的鎖。
public class StaticSynDemo {
 private static String a="test";
 public void print2(String b){
   synchronized (this) {//取得StaticSynDemo例項化後物件的鎖
    System.out.println(b+a);
   }
 }
  
 public static void print4(String b){
   synchronized (StaticSynDemo.class) { //取得StaticSynDemo.class類的鎖
    System.out.println(b+a);
   }
 }

}

 2. ReentrantLock: 
 ReentrantLock提供了多樣化的同步,比如有時間限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在資源競爭不激烈的情形下,效能稍微比synchronized差點點。但是當同步非常激烈的時候,synchronized的效能一下子能下降好幾十倍。而ReentrantLock確還能維持常態。 

 3. Atomic: 
 和上面的類似,不激烈情況下,效能比synchronized略遜,而激烈的時候,也能維持常態。激烈的時候,Atomic的效能會優於ReentrantLock一倍左右。但是其有一個缺點,就是隻能同步一個值,一段程式碼中只能出現一個Atomic的變數,多於一個同步無效。因為他不能在多個Atomic之間同步。

 4. Lock 
 Lock的鎖定是通過程式碼實現的,而 synchronized 是在 JVM 層面上實現的 。
 所以,我們寫同步的時候,優先考慮synchronized,如果有特殊需要,再進一步優化。ReentrantLock和Atomic如果用的不好,不僅不能提高效能,還可能帶來災難。

 5. ThreadLocal? 
 ThreadLocal以空間換取時間,提供了一種非常簡便的多執行緒實現方式。因為多個執行緒併發訪問無需進行等待,所以使用ThreadLocal會獲得更大的效能。雖然使用ThreadLocal會帶來更多的記憶體開銷,但這點開銷是微不足道的。因為儲存在ThreadLocal中的物件,通常都是比較小的物件。另外使用ThreadLocal不能使用原子型別,只能使用Object型別。ThreadLocal的使用比synchronized要簡單得多。 

 -- 常見的對於 ThreadLocal的介紹:
1.ThreadLocal為解決多執行緒程式的併發問題提供了一種新的思路;
2.ThreadLocal的目的是為了解決多執行緒訪問資源時的共享問題;
3.ThreadLocal 適用於每個執行緒需要自己獨立的例項且該例項需要在多個方法中被使用,也即變數線上程間隔離而在方法或類間共享的場景。

 -- ThreadLocal 適用於如下兩種場景:
1.每個執行緒需要有自己單獨的例項
2.例項需要在多個方法中共享,但不希望被多執行緒共享

  ThreadLocal 並不解決執行緒間共享資料的問題;ThreadLocal 通過隱式的在不同執行緒內建立獨立例項副本避免了例項執行緒安全的問題。
  每個執行緒持有一個 Map 並維護了 ThreadLocal 物件與具體例項的對映,該 Map 由於只被持有它的執行緒訪問,故不存線上程安全以及鎖的問題:
 1.ThreadLocalMap 的 Entry 對 ThreadLocal 的引用為弱引用,避免了 ThreadLocal 物件無法被回收的問題
 2.ThreadLocalMap 的 set 方法通過呼叫 replaceStaleEntry 方法回收鍵為 null 的 Entry 物件的值(即為具體例項)以及 Entry 物件本身從而防止記憶體洩漏
 3.ThreadLocal 適用於變數線上程間隔離且在方法間共享的場景

-------
 使用ThreadLocal的典型場景正如上面的資料庫連線管理,執行緒會話管理等場景,只適用於獨立變數副本的情況,如果
變數為全域性共享的,則不適用在高併發下使用。
 每個ThreadLocal只能儲存一個變數副本,如果想要上線一個執行緒能夠儲存多個副本以上,就需要建立多個ThreadLocal
。ThreadLocal內部的ThreadLocalMap鍵為弱引用,會有記憶體洩漏的風險。
  適用於無狀態,副本變數獨立後不影響業務邏輯的高併發場景。如果如果業務邏輯強依賴於副本變數,則不適合用
ThreadLocal解決,需要另尋解決方案。
 ThreadLocal是一個本地執行緒副本變數工具類。主要用於將私有執行緒和該執行緒存放的副本物件做一個對映,各個執行緒之
間的變數互不干擾,在高併發場景下,可以實現無狀態的呼叫,特別適用於各個執行緒依賴不同的變數值完成操作的場景

 -- ThreadLocal用法詳解和原理:
1、ThreadLocal.get: 獲取ThreadLocal中當前執行緒共享變數的值。
2、ThreadLocal.set: 設定ThreadLocal中當前執行緒共享變數的值。
3、ThreadLocal.remove: 移除ThreadLocal中當前執行緒共享變數的值。
4、ThreadLocal.initialValue: ThreadLocal沒有被當前執行緒賦值時或當前執行緒剛呼叫remove方法後呼叫get方法,返回
此方法值。

 6. transient
 Java語言的關鍵字,變數修飾符,如果用transient宣告一個例項變數,當物件儲存時,它的值不需要維持。這裡的物件儲存是指,Java的serialization提供的一種持久化物件例項的機制。當一個物件被序列化的時候,transient型變數的值不包括在序列化的表示中,然而非transient型的變數是被包括進去的。使用情況是:當持久化物件時,可能有一個特殊的物件資料成員,我們不想用serialization機制來儲存它。為了在一個特定物件的一個域上關閉serialization,可以在這個域前加上關鍵字transient。

 7. volatile
 volatile是輕量級的synchronized,它只是用來保證共享變數的可見性,不能保證操縱的原子性。volatile如何實現記憶體可見性?深入的說,通過加入記憶體屏障和禁止重排序優化實現的。
 對volatile變數執行寫操作時,會在寫操作後加入一條store屏障指令。
 對volatile變數執行讀操作時,會在讀操作前加入一條load屏障指令。
volatile保證共享變數可見性,有volatile修飾的變數進行寫操作的時候會多出一行彙編程式碼,該行程式碼會有一個lock指令。

  volatile的兩條實現原則:
①: Lock字首指令會引起處理器快取會寫到記憶體(使處理器獨佔任何共享記憶體)。
②:一個處理器的快取回寫會導致其他處理器的快取無效。

  Volatile 變數具有 synchronized 的可見性特性,但是不具備原子特性。這就是說執行緒能夠自動發現 volatile 變數的最新值。Volatile 變數可用於提供執行緒安全,但是隻能應用於非常有限的一組用例:多個變數之間或者某個變數的當前值與修改後值之間沒有約束。因此,單獨使用 volatile 還不足以實現計數器、互斥鎖或任何具有與多個變數相關的不變式(Invariants)的類(例如 “start <=end”)
  出於簡易性或可伸縮性的考慮,您可能傾向於使用 volatile 變數而不是鎖。當使用 volatile 變數而非鎖時,某些習慣用法(idiom)更加易於編碼和閱讀。此外,volatile 變數不會像鎖那樣造成執行緒阻塞,因此也很少造成可伸縮性問題。在某些情況下,如果讀操作遠遠大於寫操作,volatile 變數還可以提供優於鎖的效能優勢。

  要使 volatile 變數提供理想的執行緒安全,必須同時滿足下面兩個條件:
1.對變數的寫操作不依賴於當前值。
2.該變數沒有包含在具有其他變數的不變式中。

volatile關鍵字使用規則 http://blog.csdn.net/endlu/article/details/51180065
  volatile關鍵字是與Java的記憶體模型有關的.volatile,可變的,易變的。在DSP中,一些暫存器的值的變化有兩種情況:(1)硬體上導致的變化,例如中斷、ADC等;(2)軟體上的變化,例如對某個變數賦值等。
  當加入了關鍵字volatile,則表示該變數的值可因上述兩種情況而發生變化;即,對軟體來說,硬體上變化的值是不可預知的,加入了該關鍵字,提示編譯器每次讀取該變數時,都要直接讀取該變數地址中的暫存器,保證了資料的正確性。
  快取一致性協議出名的就是Intel 的MESI協議.併發程式設計中,我們通常會遇到以下三個問題:原子性問題,可見性問題,有序性問題.

-- 結合使用 volatile 和 synchronized 實現 “開銷較低的讀-寫鎖”
@ThreadSafe
public class CheesyCounter {
    // Employs the cheap read-write lock trick
    // All mutative operations MUST be done with the 'this' lock held
    @GuardedBy("this") 
    private volatile int value;
    public int getValue() { 
    return value;
    }
    public synchronized int increment() {
        return value++;
    }
}
  之所以將這種技術稱之為 “開銷較低的讀-寫鎖” 是因為您使用了不同的同步機制進行讀寫操作。因為本例中的寫操作違反了使用 volatile 的第一個條件,因此不能使用 volatile 安全地實現計數器 —— 您必須使用鎖。然而,您可以在讀操作中使用 volatile 確保當前值的可見性,因此可以使用鎖進行所有變化的操作,使用 volatile 進行只讀操作。其中,鎖一次只允許一個執行緒訪問值,volatile 允許多個執行緒執行讀操作,因此當使用 volatile 保證讀程式碼路徑時,要比使用鎖執行全部程式碼路徑獲得更高的共享度 —— 就像讀-寫操作一樣。然而,要隨時牢記這種模式的弱點:如果超越了該模式的最基本應用,結合這兩個競爭的同步機制將變得非常困難。

  volatile:Java 語言提供了一種稍弱的同步機制,即 volatile 變數.用來確保將變數的更新操作通知到其他執行緒,保證了新值能立即同步到主記憶體,以及每次使用前立即從主記憶體重新整理. 當把變數宣告為volatile型別後,編譯器與執行時都會注意到這個變數是共享的.
  volatile修飾的變數可以實現基本的載入和賦值的原子性.在JDK1.5之前我們只能通過 synchronized(阻塞的方式)實現這些複合操作的原子性,在JDK1.5中java.util.concurrent.atomic 包提供了若干個類能實現對int,long,boolean,reference的幾個特殊方法非阻塞原子性。

> 物件鎖和類鎖 
  加鎖非靜態方法,即是物件鎖。synchronized修飾非靜態方法、同步程式碼塊的synchronized (this)用法和synchronized (非this物件)的用法鎖的是物件,執行緒想要執行對應同步程式碼,需要獲得物件鎖。
 synchronized 加到 static 方法前面是給class 加鎖,即類鎖;而synchronized 加到非靜態方法前面是給物件上鎖。

執行緒同步的方法:sychronized、lock、reentrantLock分析 

 物件鎖是用來控制例項方法之間的同步,類鎖是用來控制靜態方法(或靜態變數互斥體)之間的同步。 
 synchronized修飾非靜態方法、同步程式碼塊的synchronized (this)用法和synchronized (非this物件)的用法鎖的是物件,執行緒想要執行對應同步程式碼,需要獲得物件鎖。
 

synchronized修飾靜態方法以及同步程式碼塊的synchronized (類.class)用法鎖的是類,執行緒想要執行對應同步程式碼,需要獲得類鎖。ThreadLocal提供了執行緒安全的共享物件,在編寫多執行緒程式碼時,可以把不安全的變數封裝進ThreadLocal。

-- Synchronized、Lock、ReentrantLock的區別:
 Synchronzied實現同步的表現形式分為:程式碼塊同步和方法同步。
 Lock,鎖物件。在Java中鎖是用來控制多個執行緒訪問共享資源的方式,一般來說,一個鎖能夠防止多個執行緒同時訪問共享資源.
  ReentrantLock lock = new ReentrantLock(); //引數預設false,不公平鎖    
lock.lock(); //如果被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果   
try {    
    //操作    
} finally {    
    lock.unlock();  //釋放鎖  
}
 Java裡面內建鎖(synchronized)和Lock(ReentrantLock)都是可重入的。在Java中,synchronized就不是可中斷鎖,而Lock是可中斷鎖。

-- synchronized和ReentrantLock的比較 區別:
1)Lock是一個介面,而synchronized是Java中的關鍵字,synchronized是內建的語言實現;
2)synchronized在發生異常時,會自動釋放執行緒佔有的鎖,因此不會導致死鎖現象發生;而Lock在發生異常時,如果沒有主動通過unLock()去釋放鎖,則很可能造成死鎖現象,因此使用Lock時需要在finally塊中釋放鎖;
 3)Lock可以讓等待鎖的執行緒響應中斷,而synchronized卻不行,使用synchronized時,等待的執行緒會一直等待下去,不能夠響應中斷;
 4)通過Lock可以知道有沒有成功獲取鎖,而synchronized卻無法辦到。
 5)Lock可以提高多個執行緒進行讀操作的效率。
 總結:ReentrantLock相比synchronized,增加了一些高階的功能。但也有一定缺陷。

  在JDK1.5中,synchronized是效能低效的。因為這是一個重量級操作,它對效能最大的影響是阻塞的是實現,掛起執行緒和恢復執行緒的操作都需要轉入核心態中完成,這些操作給系統的併發性帶來了很大的壓力。相比之下使用Java提供的ReentrankLock物件,效能更高一些。到了JDK1.6,發生了變化,對synchronize加入了很多優化措施,有自適應自旋,鎖消除,鎖粗化,輕量級鎖,偏向鎖等等。導致在JDK1.6上synchronize的效能並不比Lock差。官方也表示,他們也更支援synchronize,在未來的版本中還有優化餘地,所以還是提倡在synchronized能實現需求的情況下,優先考慮使用synchronized來進行同步。
  根類 Object 包含某些特殊的方法,用來線上程的 wait() 、 notify() 和 notifyAll() 之間進行通訊。

 ReentrantLock(CAS,AQS,java記憶體可見性(voliate))是可重入的獨佔鎖或者叫排他鎖。同時只能有一個執行緒獲取該鎖,其實現分為公平實現和非公平實現。
 由於Volatile不能保證操作的原子性,因此,一般情況下,Volatile不能代替Synchronized。此外,使用Volatile會阻止編譯器對程式碼的優化,因此會降低程式的執行效率。除非迫不得已,一般能不用就不要用。
 

> Synchronized與ThreadLocal區別:
  Synchronized用於執行緒間的資料共享,而ThreadLocal則用於執行緒間的資料隔離。
  ThreadLocal和Synchonized都用於解決多執行緒併發訪問。但是ThreadLocal與synchronized有本質的區別。synchronized是利用鎖的機制,使變數或程式碼塊在某一時該只能被一個執行緒訪問。而ThreadLocal為每一個執行緒都提供了變數的副本,使得每個執行緒在某一時間訪問到的並不是同一個物件,這樣就隔離了多個執行緒對資料的資料共享。而Synchronized卻正好相反,它用於在多個執行緒間通訊時能夠獲得資料共享。 
  當然ThreadLocal並不能替代synchronized,它們處理不同的問題域。Synchronized用於實現同步機制,比ThreadLocal更加複雜。 

 ThreadLocal是一個執行緒隔離(或者說是執行緒安全)的變數儲存的管理實體(注意:不是儲存用的),它以Java類方式表現;
 synchronized是Java的一個保留字,只是一個程式碼識別符號,它依靠JVM的鎖機制來實現臨界區的函式、變數在CPU執行訪問中的原子性。
 Java提供了同步機制來解決併發問題。synchonzied關鍵字可以用來同步變數,方法,甚至同步一個程式碼塊。

  ThreadLocal與Synchronize,一個是鎖機制進行時間換空間,一個是儲存拷貝進行空間換時間。
  ThreadLocal是解決執行緒安全問題一個很好的思路,它通過為每個執行緒提供一個獨立的變數副本解決了變數併發訪問的衝突問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決執行緒安全問題更簡單,更方便,且結果程式擁有更高的併發性。

> Synchronized與Lock
 主要相同點:Lock能完成Synchronized所實現的所有功能。
 主要不同點:Lock有比Synchronized更精確的執行緒予以和更好的效能。Synchronized會自動釋放鎖,但是Lock一定要求程式設計師手工釋放,並且必須在finally從句中釋放。
   synchronized 修飾方法時 表示同一個物件在不同的執行緒中 表現為同步佇列.如果例項化不同的物件 那麼synchronized就不會出現同步效果了。
   1.物件的鎖 
所有物件都自動含有單一的鎖。 
   JVM負責跟蹤物件被加鎖的次數。如果一個物件被解鎖,其計數變為0。在任務(執行緒)第一次給物件加鎖的時候,計數變為1。每當這個相同的任務(執行緒)在此物件上獲得鎖時,計數會遞增。 
   只有首先獲得鎖的任務(執行緒)才能繼續獲取該物件上的多個鎖。 
每當任務離開一個synchronized方法,計數遞減,當計數為0的時候,鎖被完全釋放,此時別的任務就可以使用此資源。 
    2.synchronized同步塊 
2.1同步到單一物件鎖 
當使用同步塊時,如果方法下的同步塊都同步到一個物件上的鎖,則所有的任務(執行緒)只能互斥的進入這些同步塊。

> Lock的使用參見下面的程式碼(把Lock換成synchronized的效果是一樣的)
import java.util.concurrent.TimeUnit;  
import java.util.concurrent.locks.Lock;  
import java.util.concurrent.locks.ReentrantLock;  
  
public class Resource3 {  
    private Lock lock = new ReentrantLock();  
    public void f() {  
      // other operations should not be locked...  
  
        System.out.println(Thread.currentThread().getName()  
                + ":not synchronized in f()");  
        lock.lock();  
        try {  
            for (int i = 0; i < 5; i++) {  
                System.out.println(Thread.currentThread().getName()  
                        + ":synchronized in f()");  
                try {  
                    TimeUnit.SECONDS.sleep(3);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    public void g() {  
        // other operations should not be locked...  
        System.out.println(Thread.currentThread().getName()  
                + ":not synchronized in g()");  
        lock.lock();  
        try {  
            for (int i = 0; i < 5; i++) {  
                System.out.println(Thread.currentThread().getName()  
                        + ":synchronized in g()");  
                try {  
                    TimeUnit.SECONDS.sleep(3);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    public void h() {  
        // other operations should not be locked...  
        System.out.println(Thread.currentThread().getName()  
                + ":not synchronized in h()");  
        lock.lock();  
        try {  
            for (int i = 0; i < 5; i++) {  
                System.out.println(Thread.currentThread().getName()  
                        + ":synchronized in h()");  
                try {  
                    TimeUnit.SECONDS.sleep(3);  
                } catch (InterruptedException e) {  
                    e.printStackTrace();  
                }  
            }  
        } finally {  
            lock.unlock();  
        }  
    }  
  
    public static void main(String[] args) {  
        final Resource3 rs = new Resource3();  
        new Thread() {  
            public void run() {  
                rs.f();  
            }  
        }.start(); 
 
        new Thread() {  
            public void run() {  
                rs.g();  
            }  
        }.start(); 
 
        rs.h();  
    }  

相關文章