自旋鎖學習系列(3):指數後退技術

上一篇中分析了測試鎖的兩種實現TASLock和TTASLock,主要對這兩種鎖的性能進行了分析。對於TTASLock,咱們知道比TASLock性能上要好不少,具體分析已經講過了。咱們最後也說了,TTASLock雖然比TASLock大有改進,可是在性能上仍是不夠理想。這一篇的目的就是針對TTASLock作一下改進。 java

咱們再來看一下TTASLock的實現源碼和加鎖的流程圖: 緩存


/**
 * 
 * Test test and set lock
 * 
 */
public class TTASLock {
	private AtomicBoolean state = new AtomicBoolean(false);

	// 加鎖
	public void lock() {
		while (true) {
			while (state.get()) {
				// 自旋
			}
			if (!state.getAndSet(true)) {
				break;
			}
		}
	}

	// 解鎖
	public void unlock() {
		state.set(false);
	}
}
加鎖流程圖以下:


從上文咱們知道,對於TTASLock鎖,性能問題主要出如今解鎖上。一旦一個已經得到鎖的線程執行解鎖操做。其餘線程都會產生緩存缺失,將會由」本地自旋」轉變爲從共享服務器中去獲取狀態值。這會消耗大量的總線資源。因此,若是咱們要對TTASLock改進的話,須要從這裏去想辦法。 服務器

這裏咱們就要一直成爲指數後退的技術。「指數後退」名字聽起來挺嚇人的,可是原理上很簡單,實現上也不是多複雜的事情。我舉個例子你們都明白了。咱們用迅雷帳戶登陸的時候,若是網絡斷掉了。迅雷會從新嘗試登陸。第一次嘗試多是5秒鐘之後。若是第一次嘗試失敗,第二次嘗試就會在10秒鐘之後登陸。依次類推,失敗的次數越多,嘗試登陸的延時時間就越長,成指數性「後退」。很簡單吧。 網絡

迅雷掉線「指數後退」方式的重登錄。 dom

這個和加鎖有什麼關係呢? 性能

咱們再來深刻看一下TTASLock的加鎖過程,一旦一個線程獲取不到鎖,它會一直的在本地自旋等待。若是有一百多個線程爭鎖,就有99個(有一個得到了鎖)在本地自旋等待。而那個得到鎖的線程一旦釋放鎖,這99個線程都會產生緩存缺失。一直總線風暴以後,仍是隻有一個線程能得到鎖。其餘的依然在本地不斷的自旋等待。過程分析完了,發現什麼問題了嗎?問題在於既然每次只有一個線程得到鎖,須要全部的線程同時自旋等待嗎?咱們難道不能讓等待線程「指數後退」嗎? 測試

OK,理論到此爲止。讓咱們按照這個思路來實現吧。既然指數後退,咱們先實現這部分功能。咱們用一個BackOff類來表明這個抽象。 .net


public class Backoff {

	// 須要對後退時間設置一個最大值和最小值
	final int minDelay, maxDelay;
	int limit;
	final Random random;

	public Backoff(int min, int max) {
		maxDelay = max;
		minDelay = min;
		limit = minDelay;
		random = new Random();
	}

	public void backoff() throws InterruptedException {
		// 計算須要睡眠的時間
		int delay = random.nextInt(limit);
		// 從新計算limit,每次兩倍增長。但最大等於maxDelay
		limit = Math.min(maxDelay, 2 * limit);
		// 當前線程睡眠
		Thread.sleep(delay);

	}
}
而後咱們把它用來改造咱們的TTASLock:



class BackoffLock {
        private AtomicBoolean state = new AtomicBoolean(false);
        private static final int MIN_DELAY = ...;
        private static final int MAX_DELAY = ...;

        // 加鎖
        public void lock() throws InterruptedException {
            Backoff back = new Backoff(MIN_DELAY, MAX_DELAY);
            while (true) {
                while (state.get()) {
                    // 自旋
                }
                if (!state.getAndSet(true)) {
                    return;
                } 
                //關鍵點,一旦獲取鎖失敗,就指數後退
                else {
                    back.backoff();
                }
            }
        }
        // 解鎖
        public void unlock() {
            state.set(false);
        }
    }

上面實現和TTASLock相比就多了指數後退這一步操做。其餘的都沒有什麼變化。BackoffLock性能上比TAS系列要好的多。由於它不會在同時(隨機的妙用哦)存在大量的線程去爭鎖,不會形成總線風暴。BackoffLock最關鍵的地方是肯定睡眠的最小值和最大值,這個須要大量的測試才能肯定合適的值,並且這兩個值對性能影響很是大。因此BackoffLock鎖的可移植性很差,由於不一樣的機器對這兩個值都有不一樣的要求。 線程

正由於BackoffLock不是很是的完美,因此引出了咱們下一個主角的出現:隊列鎖。 code

相關文章
相關標籤/搜索