ReentrantReadWriteLock讀寫鎖簡單原理案例證明

ReentrantReadWriteLock存在原因?


我們知道List的實現類ArrayList,LinkedList都是非線程安全的,Vector類通過用synchronized修飾方法保證了List的多線程非安全問題,但是有個缺點:讀寫同步,效率低下。於是就出現了CopyOnWriteArrayList,它通過寫時複製數組實現了讀寫分離,提高了多線程對List讀的效率,適合多讀少些的情況。同理:我們知道ReentrantLock,它是一把獨佔的鎖,是用來控制線程同步的,如果我們用ReentrantLock來實現ArrayList安全,能否達到CopyOnWriteArrayList同樣的效果呢?

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-26 15:49
 * @description:ReentrantReadWriteLock多讀少寫的場景
 * @modified By:
 * 公衆號:叫練
 */
public class MultReadTest {


    private static class MyList {
        private final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
        private final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
        private final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();
        private List<String> list = new ArrayList();

        //讀list
        public void readList() {
            try {
                READ_LOCK.lock();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":"+list.size());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                READ_LOCK.unlock();
            }
        }

        //寫list
        public void writeList() {
            try {
                WRITE_LOCK.lock();
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName()+":新增1個元素");
                list.add("叫練【公衆號】");

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                WRITE_LOCK.unlock();
            }
        }

    }

    public static void main(String[] args) {
        MyList myList = new MyList();
        //讀寫鎖適合多讀少寫情況
        //新建10個讀線程,1個寫線程
        new Thread(()->{myList.writeList();},"寫線程").start();
        for (int i=0; i<10; i++) {
            new Thread(()->{myList.readList();},"讀線程"+(i+1)).start();
        }
    }

}

上面案例我們用ReentrantReadWriteLock實現了CopyOnWriteArrayList,主線程新建了1個寫線程寫list,10個讀線程讀list,程序一共花費2執行完畢,如果用Vector需要花費11秒。在多線程的情況下,通過讀寫鎖操作List,提高了List的讀效率,在List讀的部分,線程是共享的,在對List寫的過程中,在對寫的線程是同步的,因此我們可以得出一個結論:讀寫鎖是讀讀共享,讀寫同步



獨佔獲取鎖簡單流程


image.png

如上圖,我們簡單的梳理下獨佔獲取鎖流程。

  1. 獨佔鎖獲取(上述例子中的WRITE_LOCK寫鎖),首先判斷是否有線程獲取了鎖,是否有線程獲取了鎖的判斷通過讀寫鎖中通過32位int類型state可以獲取,其中低16位表示讀鎖,高16表示寫鎖。
  2. 有讀鎖:直接排隊阻塞。
  3. 有寫鎖:還需要判斷寫鎖線程是否是自己,如果是自己就是鎖重入了,如果不是自己說明已經有其他的線程獲取鎖正在執行,那麼當前線程需要排隊阻塞。
  4. 無鎖:直接獲取鎖,其他搶佔的獨佔鎖線程需要排隊阻塞,當前線程執行完畢後釋放鎖通知下一個排隊線程獲取鎖。


共享獲取鎖簡單流程


image.png

如上圖,我們簡單的梳理下共享鎖獲取鎖流程。

  1. 獨佔鎖獲取(上述例子中的READ_LOCK讀鎖),首先判斷是否有線程獲取了鎖。
  2. 有讀鎖:當前線程發現此時讀鎖狀態被佔用,說明有線程獲取了讀鎖。該線程通過cas自旋【死循環】獲取到讀鎖爲止。
  3. 有寫鎖:還需要判斷持有寫鎖的線程是否是自己,如果是自己而且此時是獲取的是讀鎖會獲取鎖成功,我們稱爲鎖降級,如果不是自己說明此時有其他線程獲取了寫鎖,那麼當前線程需要排隊阻塞。
  4. 無鎖:直接獲取鎖。


寫鎖降級


我們說讀寫互斥,但同一個線程中,先寫後讀也是允許的,我們稱之爲鎖降級。在面試中共享鎖面試頻率也比較高,方便理解我們舉個簡單的案例說明下。

import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author :jiaolian
 * @date :Created in 2021-01-28 15:44
 * @description:ReentrantReadWriteLock讀寫鎖降級測試
 * @modified By:
 * 公衆號:叫練
 */
public class WriteLockLowerTest {

    private static final ReentrantReadWriteLock REENTRANT_READ_WRITE_LOCK = new ReentrantReadWriteLock();
    private static final ReentrantReadWriteLock.WriteLock WRITE_LOCK = REENTRANT_READ_WRITE_LOCK.writeLock();
    private static final ReentrantReadWriteLock.ReadLock READ_LOCK = REENTRANT_READ_WRITE_LOCK.readLock();

    public static void main(String[] args) {
        try {
            WRITE_LOCK.lock();
            System.out.println("獲取寫鎖");
            READ_LOCK.lock();
            System.out.println("獲取讀鎖");
        } finally {
            READ_LOCK.unlock();
            System.out.println("釋放寫鎖");
            WRITE_LOCK.unlock();
            System.out.println("釋放讀鎖");
        }
    }
}

如上述代碼:程序可以運行完畢,說明鎖可以降級。另外說一句,上面的程序先獲取讀鎖再獲取寫鎖,程序是會阻塞的,爲什麼呢?歡迎小夥伴在留言區寫下評論!



總結


今天我們用通俗易懂的文字描述了ReentrantReadWriteLock讀寫鎖。喜歡的請點贊加評論哦!點關注,不迷路,我是叫練【公衆號】,邊叫邊練。期待我們下次再見!

tempimage1611629165941.gif




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