JUC併發工具四-ReadWriteLock

目錄

 

1 讀寫鎖規則

2 認識ReadWriteLock接口

3 寫鎖Sync類中基本變量

4 寫鎖加鎖方法

5 寫鎖解鎖方法

6 讀鎖加鎖方法

7 讀鎖釋放


1 讀寫鎖規則

讀-讀可以共存,讀-寫不能共存,寫-寫不能共存2 讀寫鎖使用

    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

    // 讀方法
    private void read() {
        readWriteLock.readLock().lock();
        System.out.println(Thread.currentThread() + "read start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + "read end");
        readWriteLock.readLock().unlock();
    }

    // 寫方法
    private void write() {
        readWriteLock.writeLock().lock();
        System.out.println(Thread.currentThread() + "write start");
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + "write end");
        readWriteLock.writeLock().unlock();
    }

測試代碼

    @Test
    public void readWriteLockTest() throws InterruptedException {
        for (int i = 0; i < 5; i ++) {
            new Thread(() -> read()).start();
        }
        for (int i = 0; i < 5; i ++) {
            new Thread(() -> write()).start();
        }
        TimeUnit.MINUTES.sleep(1);
    }

輸出
Thread[Thread-0,5,main]read start
Thread[Thread-1,5,main]read start
Thread[Thread-2,5,main]read start
Thread[Thread-3,5,main]read start
Thread[Thread-3,5,main]read end
Thread[Thread-0,5,main]read end
Thread[Thread-1,5,main]read end
Thread[Thread-2,5,main]read end
Thread[Thread-5,5,main]write start
Thread[Thread-5,5,main]write end
Thread[Thread-6,5,main]write start
Thread[Thread-6,5,main]write end
Thread[Thread-4,5,main]read start
Thread[Thread-4,5,main]read end
Thread[Thread-7,5,main]write start
Thread[Thread-7,5,main]write end
Thread[Thread-8,5,main]write start
Thread[Thread-8,5,main]write end
Thread[Thread-9,5,main]write start
Thread[Thread-9,5,main]write end
當然每次執行結果可能不一樣,但是總體可以得出結論,讀讀可以併發,讀寫不能併發,寫寫不呢併發3 讀寫鎖原理

2 認識ReadWriteLock接口


java中讀寫鎖是實現了ReadWriteLock接口,這個接口比較簡單,只有兩個方法

public interface ReadWriteLock {
    /**
     * @return 獲取讀鎖
     */
    Lock readLock();

    /**
     * @return 獲取寫鎖
     */
    Lock writeLock();
}


ReentrantReadWriteLock方法的實現

public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }


可以看到返回的是ReentrantReadWriteLock的內部類

3 寫鎖Sync類中基本變量

/**
 * 讀寫鎖共用同步狀態變量,來同時維護讀和寫的狀態,按位切割使用,高16位表示讀的狀態,低16位表示寫的狀態
 */
//讀鎖狀態偏移量,左移16位16位
static final int SHARED_SHIFT   = 16;
//讀鎖操作的基本單元,讀鎖狀態+1,則狀態變量值+SHARED_UNIT
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
//可重入狀態的最大值
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
//寫鎖掩碼,將同步狀態變量和掩碼位與就可以得出寫鎖的狀態
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

4 寫鎖加鎖方法

public void lock() {
    sync.acquire(1);
}


可以看到用到的還是AQS中的模版方法,acquireShared方法執行過程在筆者JUC併發工具二-AQS中講過,感興趣的同學可以先了解一下
然後看acquireShared方法中的第一步tryAcquireShared在讀鎖中的實現

protected final boolean tryAcquire(int acquires) {

    Thread current = Thread.currentThread();
    // 獲取當前state資源的值
    int c = getState();
    // 獲取當前寫鎖狀態
    int w = exclusiveCount(c);
    if (c != 0) {
        // 如果c!=0並且w=0表示持有共享鎖,獲取資源失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            // 寫鎖重入次數已超過最大值
            throw new Error("Maximum lock count exceeded");
        // 獲取資源成功,修改state變量
        setState(c + acquires);
        return true;
    }
    // 對於非公平鎖,writerShouldBlock總是false;對於公平鎖就是在重入鎖章節中講到的hasQueuedPredecessors方法
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 設置當前線程佔用寫鎖資源
    setExclusiveOwnerThread(current);
    return true;
}

5 寫鎖解鎖方法

public void unlock() {
    sync.release(1);
}


AQS章節中已經講過release方法執行過程,這裏直接看tryRelease代碼,和ReentrantLock代碼差不多

protected final boolean tryRelease(int releases) {
    if (!isHeldExclusively())
        // 如果不是佔用線程不是當前線程直接拋異常
        throw new IllegalMonitorStateException();
    // 查看剩餘需要解鎖次數
    int nextc = getState() - releases;
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        // 如果當前線程解鎖完畢釋放線程佔用
        setExclusiveOwnerThread(null);
    setState(nextc);
    return free;
}

6 讀鎖加鎖方法

public void lock() {
    sync.acquireShared(1);
}


可以看到用到的還是AQS中的模版方法,acquireShared方法執行過程在筆者JUC併發工具二-AQS中講過,感興趣的同學可以先了解一下
然後看acquireShared方法中的第一步tryAcquireShared在讀鎖中的實現

protected final int tryAcquireShared(int unused) {
    Thread current = Thread.currentThread();
    int c = getState();
    if (exclusiveCount(c) != 0 &&
        getExclusiveOwnerThread() != current)
        // 如果有寫鎖佔用並且佔用線程不是當前線程的話返回-1,獲取資源失敗
        return -1;
    int r = sharedCount(c);
    // readerShouldBlock根據公平和非公平策略限制
    if (!readerShouldBlock() &&
        r < MAX_COUNT &&
        // 如果不阻塞並且寫鎖重入次數小於閾值則對資源加上讀數量
        compareAndSetState(c, c + SHARED_UNIT)) {
        if (r == 0) {
            firstReader = current;
            firstReaderHoldCount = 1;
        } else if (firstReader == current) {
            firstReaderHoldCount++;
        } else {
            HoldCounter rh = cachedHoldCounter;
            if (rh == null || rh.tid != getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
        }
        return 1;
    }
    //如果獲取失敗則循環獲取
    return fullTryAcquireShared(current);
}

7 讀鎖釋放

public void unlock() {
    sync.releaseShared(1);
}

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            // 把firstReader擲空
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        // 自旋更新state值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}

 

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