三, 基礎篇系列,JAVA的併發包 - 鎖

JAVA中主要鎖

synchronizedphp

Reentrantlockhtml

ReentrantReadWriteLockjava

 

問題引入

爲何須要鎖?緩存

爲何JAVA有了synchronize還須要Reentrantlock和ReentrantReadWriteLock?優化

synchronize和lock分別怎麼實現同步快(原子性,一致性,禁重排序)?ui

synchronize和lock分別怎麼實現鎖的優化,可重入鎖,偏向鎖?atom

lock如何實現公平鎖(synchronize是非公平鎖)?spa

 

爲何須要鎖?

目的:.net

鎖的目的是防止資源的競爭,主要從  原子性(一致性),可見性,防止處理重排序 三個方面來處理, volatile知足了後面兩個特性,JAVA從兩方面來實現鎖線程

 

爲何JAVA有了synchronize還須要Reentrantlock和ReentrantReadWriteLock?

 

synchronized與ReentrantLock ,使用上看區別

1, synchronize在獲取鎖阻塞的時候是不能打斷的

2, synchronize無超時機制,阻塞了的話只能一直阻塞形成死鎖

3,synchronize只能notify,wait,若是須要兩個或以上條件就不能用了,如: JAVA阻塞隊列的實現,須要用是否爲空和是否已滿兩個條件來阻塞線程

看lock相關的API就知道, 主要就是解決這幾個問題

方法名稱 描述
lock 獲取鎖,若是鎖沒法獲取,那麼當前的線程就變爲不可被調度,直到鎖被獲取到
lockInterruptibly 獲取鎖,除非當前線程被中斷。若是獲取到了鎖,那麼當即返回,若是獲取不到,那麼當前線程變得不可被調度,一直休眠直到下面兩件事情發生:一、當前線程獲取到了鎖

 

二、其餘的線程中斷了當前的線程

tryLock 若是調用的時候可以獲取鎖,那麼就獲取鎖而且返回true,若是當前的鎖沒法獲取到,那麼這個方法會馬上返回false
tryLcok(long time,TimeUnit unit) 在指定時間內嘗試獲取鎖若是能夠獲取鎖,那麼獲取鎖而且返回true,若是當前的鎖沒法獲取,那麼當前的線程變得不可被調度,直到下面三件事之一發生:一、當前線程獲取到了鎖

 

二、當前線程被其餘線程中斷

三、指定的等待時間到了

unlock 釋放當前線程佔用的鎖
newCondition 返回一個與當前的鎖關聯的條件變量。在使用這個條件變量以前,當前線程必須佔用鎖。調用Condition的await方法,會在等待以前原子地釋放鎖,並在等待被喚醒後原子的獲取鎖
 

 

那ReentrantReadWriteLock呢?

讀寫鎖用於讀多寫少的狀況,即當一條線程獲取寫鎖後,後面的讀鎖都被阻塞,等待獲取寫鎖的線程完成釋放。

場景,如本地緩存失效,當須要去DB拿數據進行寫入的操做,須要阻塞其它讀的操做.

固然,讀寫鎖也是能夠基於notifyAll和wait實現

須要注意的是

  1. 若是無寫鎖,讀是不阻塞,
  2. 持有讀鎖後,不能直接調用寫鎖的lock方法 ,不然會形成死鎖

 

synchronize和lock分別怎麼實現同步快(原子性,一致性,禁重排序)?

synchronized鎖

實現依賴

原子性,可見性和重排序都是依靠指令。方法同步和代碼塊同步依靠Monitor指令,代碼塊同步是使用monitorenter和monitorexit指令實現

鎖信息保存在JAVA對象頭裏,準確說是Mark Word

synchronize的阻塞,依靠幾個隊列,屬於不公平鎖(線程先CAS競爭鎖,再進隊列)

ContentionList(LIFO)-->EntryList(LIFO)-->OnDeck-->Owner-->Wait Set  (http://www.cnblogs.com/lykm02/p/4516777.html )

鎖的轉換方面

無鎖-->偏向鎖-->輕量鎖-->重量鎖  (http://blog.csdn.net/xad707348125/article/details/47189107)

 

synchronize和lock分別怎麼實現鎖的優化,可重入鎖,偏向鎖?

偏向鎖和可重入鎖的實現

可重入鎖,即當本線程進入同一鎖時能夠進行屢次上鎖,固然也須要屢次釋放

偏向鎖,即當獲取線程再次進入同步塊時不須要再次競爭(CAS),當某個Core CAS成功時必然會引發總線風暴,這就是所謂的本地延遲,本質上偏向鎖就是爲了消除CAS,下降Cache一致性流量

原理:

1, 鎖保存當前鎖的線程,判斷同一個線程時容許

用一個計數器去記錄當前重入的次數,當進入時計數器+1, 當釋放鎖時計算器-1, 當爲0時表示可競爭

 

synchronized 

實現方式, 會在 Mark Word 存儲獲取鎖線程的ID,而後棧幀中也存儲線程ID,之後該線程再次進入同步塊(同步方法)時不須要花費CAS了。 

lock 

實現方式,  用JAVA代碼實現處理,跟蹤下  lock()(NonfairSyncCAS獲取鎖失敗)->acquire(AQS嘗試獲取鎖)-->tryAcquire(nonfairTryAcquire)-->nonfairTryAcquire(Sync 以下處理):

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {  //再次判斷是不是未鎖狀態,state爲0爲未有線程獲取鎖
                if (compareAndSetState(0, acquires)) { //CAS再次競爭獲取鎖,此處是公平鎖與非公平鎖的區別
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //這裏是是實現偏向鎖的關鍵,比較若是是當前鎖就不進入CLH隊列後面的競爭了
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 

lock如何實現公平鎖(synchronize是非公平鎖)?

 

synchronize自己是非公平鎖,無公平性實現.

lock非公平鎖代碼(詳細如上)

if (compareAndSetState(0, acquires)) {  //如鎖被釋放,是非公平鎖的話,用CAS再次競爭獲取鎖

公平鎖此段代碼以下:

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {//如鎖被釋放,必須當隊列爲空時纔去CAS競爭鎖  
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

 

常見的鎖實現原理總結

自旋鎖  

多個線程一塊兒用CAS去嘗試獲取一個共同的可見性(volatile)的變量,獲取成功即爲獲取鎖

 

如 全部線程都運行

/**
	 * 自旋鎖方式去實現阻塞
	 * 
	 * 缺點:沒法實現公平性,若是大量使用會增長CPU的Cache一致性流量開銷
	 */
	public static void CASLock() {
		// 不斷去獲取CAS的鎖,如成功表示獲取鎖成功
		while (state.compareAndSet(0, 1)) {
		}
	}

	public static void CASUnlock() {

		if (!state.compareAndSet(1, 0)) {
			// 釋放鎖異常
			throw new RuntimeException();
		}
	}

排隊自旋鎖 (Ticket Lock)

import java.util.concurrent.atomic.AtomicInteger;

public class TicketLock {
   private AtomicInteger serviceNum = new AtomicInteger(); // 服務號
   private AtomicInteger ticketNum = new AtomicInteger(); // 排隊號

   public int lock() {
         // 首先原子性地得到一個排隊號
         int myTicketNum = ticketNum.getAndIncrement();

              // 只要當前服務號不是本身的就不斷輪詢
       while (serviceNum.get() != myTicketNum) {
       }

       return myTicketNum;
    }

    public void unlock(int myTicket) {
        // 只有當前線程擁有者才能釋放鎖
        int next = myTicket + 1;
        serviceNum.compareAndSet(myTicket, next);
    }
}

CLH鎖

CLH是在前驅節點的屬性上自旋, 

組成一個隊列後,每一個節點都有保存當前節點獲取鎖的狀態,和前一個節點的指向,獲取鎖的步驟

1, 新加個節點,並把節點經過自旋指向tail節點

2, 成功後,不停判斷指向節點的鎖狀態,當前節點鎖釋放時獲取鎖

3, 釋放鎖,改變自身的鎖持有狀態就行

java 就是運用的CLH鎖,但有所改進,主要包括

1, 指向了head

2,   在等待機制上由原來的自旋改爲阻塞喚醒

詳細介紹: http://blog.csdn.net/chenssy/article/details/50432195 

 

MCS鎖

而MCS是在本地屬性變量上自旋。

 

資料參考: http://coderbee.net/index.php/concurrent/20131115/577/comment-page-1 

 

 

 

歡迎關注個人公衆號, 一塊兒來構建咱們的知識體系

 

相關文章
相關標籤/搜索