synchronized/Lock

一:synchronized會隱式的進行獲取鎖與釋放鎖的操做,新JDK中底層也是由CAS實現的java

對synchronized JVM 經過moniterenter和moniterexit命令包裹synchronized範圍代碼實現,隱式獲取鎖與釋放鎖對應的指令,這兩個指令劃分了一片同步塊,具備排他性,當有線程進入該同步塊後,其餘線程必須等待在monitereneter指令上,直到進入同步塊的線程經過moniterexit指令退出後,其餘線程才能夠進入同步塊。面試

從本質上來講,moniterenter與moniterexit是一組排他的對某一對象監視器進行嘗試獲取的過程(該對象正是示例中的lock),同一時刻只有一個線程成功獲取對象監視器。在線程運行到同步塊時,會經過moniterenter指令嘗試獲取對象的監視器,若是獲取成功,則進入同步塊,執行同步塊內指令,若是獲取失敗,則會進入同步隊列(SynchronizedQueue)中進行等待,線程當前狀態變爲BLOCK(阻塞)狀態,直到有線程釋放監視器。數據庫

 

二:重入鎖(ReentrantLock)安全

    synchronized/ReentrantLock的區別:多線程

  1. ReentrantLock提供了顯式加解鎖操做。提供了lock(),unlock()方法進行加解鎖的操做,而synchronized是隱式進行加鎖與解鎖操做(依賴於編譯器將其編譯爲moniterenter與moniterexit)。併發

  2. 對鎖的等待能夠中斷,在持有鎖的線程長時間不釋放鎖時,等待鎖的線程能夠選擇放棄等待,這樣就避免了synchronized可能帶來的死鎖問題。ReentrantLock.tryLock()能夠設置等待時間。函數

  3. ReentrantLock提供了公平鎖與非公平鎖,而synchronized的實現是非公平鎖spa

  4. 可以更加精細的控制多線程的休眠與喚醒。對於同一個鎖,咱們能夠建立多個Condition,在不一樣的狀況下使用不一樣的Condition。
        例如,假如多線程讀/寫同一個緩衝區:當向緩衝區中寫入數據以後,喚醒"讀線程";當從緩衝區讀出數據以後,喚醒"寫線程";而且當緩衝區滿的時候,"寫線程"須要等待;當緩衝區爲空時,"讀線程"須要等待。      .net

          若是採用Object類中的wait(), notify(), notifyAll()實現該緩衝區,當向緩衝區寫入數據以後須要喚醒"讀線程"時,不可能經過notify()或notifyAll()明確的指定喚醒"讀線程",而只能經過notifyAll喚醒全部線程(可是notifyAll沒法區分喚醒的線程是讀線程,仍是寫線程)。  可是,經過Condition,就能明確的指定喚醒讀線程。線程

  5. 讀寫分離鎖ReentrantReadWriteLock

  6. ReentrantLock是對象,比synchronized須要更多的內存消耗。

        ReentrantLock相對於synchronized來講通常用於,加鎖與解鎖操做須要分離的使用場景,例如加解鎖再也不一個函數裏(synchronized沒法用括號包圍),相對來講ReentrantLock提供了更高的靈活性,可是使用時必定不要忘了釋放鎖。

 

三:讀寫鎖(ReentrantReadWriteLock)

相對與ReentrantLock它對變量的讀寫操做進行了區別對待,遵循如下特性:

  1. 在沒有寫操做線程獲取寫鎖的狀況下,全部讀操做均可以獲取讀鎖。

  2. 在有寫線程獲取寫鎖的狀況下,讀操做等待寫線程釋放鎖後,才能夠獲取讀鎖。

  3. 在有讀線程獲取讀鎖的狀況下,寫線程會等待全部讀線程釋放鎖後,才能夠獲取寫鎖,而且與此同時,全部的讀鎖也不可獲取。

四:Java8對讀寫鎖的改進:StampedLock

讀不阻塞寫的實現思路:

        在讀的時候若是發生了寫,則應當重讀而不是在讀的時候直接阻塞寫!

使用StampedLock就能夠實現一種無障礙操做,即讀寫之間不會阻塞對方,可是寫和寫之間仍是阻塞的!

http://blog.csdn.net/sunfeizhi/article/details/52135136

 

綜上所述,就是保證讀寫不會同時發生。下面咱們經過開發一個線程安全的讀寫分離HashMap來看看讀寫鎖的具體使用:

public class ReentrantReadWriteLockHashMap {

    private final Map<String, Object> hashMap = new HashMap<String, Object>();

    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

 

    private final Lock readLock = rwLock.readLock();

    private final Lock writeLock = rwLock.writeLock();

    

    public final void put(String key, Object value){

        //上寫鎖,不容許其餘線程讀也不容許寫

        writeLock.lock();

        hashMap.put(key, value);

        writeLock.unlock();

    }

    

    public final Object get( String key ){

        //上讀鎖,其餘線程只能讀不能寫

        readLock.lock();

        Object value =  hashMap.get(key);

        readLock.unlock();

        

        return value;

    }

}

 

經過示例代碼,能夠看到讀寫鎖的使用仍是很簡單的,關鍵要理解讀寫鎖的運行機制,讀寫分離,區別對待。

 

產生死鎖的四個必要條件: 

  1. 互斥條件:一個資源每次只能被一個線程/進程使用。

  2. 請求與保持條件:一個線程/進程因請求資源而阻塞時,對已得到的資源保持不放。 

  3. 不剝奪條件:線程/進程已得到的資源,在未使用完以前,不能強行剝奪。

  4. 循環等待條件:若干線程/進程之間造成一種頭尾相接的循環等待資源關係。

死鎖的解除與預防

通常解決死鎖的途徑分爲死鎖的預防,避免,檢測與恢復這三種。

死鎖的預防是要求線程/進程申請資源時遵循某種協議,從而打破產生死鎖的四個必要條件中的一個或幾個,保證系統不會進入死鎖狀態。

死鎖的避免不限制線程/進程有關申請資源的命令,而是對線程/進程所發出的每個申請資源命令加以動態地檢查,並根據檢查結果決定是否進行資源分配。

死鎖檢測與恢復是指系統設有專門的機構,當死鎖發生時,該機構可以檢測到死鎖發生的位置和緣由,並能經過外力破壞死鎖發生的必要條件,從而使得併發進程從死鎖狀態中恢復出來。

對於java程序來講,產生死鎖時,咱們能夠用jvisualvm/jstack來分析程序產生的死鎖緣由,根據病因來根治。

 

 

一道面試題比較synchronized和讀寫鎖

第二個if比較關鍵,它避免了多餘的幾回對數據庫的讀取  ,
在lock里加if避免多線程排隊去db取數據,加了if後面阻塞的線程再拿到鎖後判斷就不爲null,直接返回結果

http://blog.csdn.net/it_man/article/details/8972001

相關文章
相關標籤/搜索