1. 程式人生 > 其它 >執行緒基礎知識14 ReentrantLock和ReentrantReadWriteLock

執行緒基礎知識14 ReentrantLock和ReentrantReadWriteLock

1 簡介

  ReentrantLock和ReentrantReadWriteLock都是可重入鎖。可重入鎖,顧名思義,就是支援重進入的鎖,它表示該鎖能夠支援一個執行緒對資源的重複加鎖

  ReentrantLock和ReentrantReadWriteLock都支援獲取鎖時的公平和非公平性選擇。預設是非公平的

  ReentrantLock讀讀、讀寫、寫寫全部互斥。ReentrantReadWriteLock讀讀共享,讀寫互斥,寫寫互斥,且支援鎖降級

  ReentrantReadWriteLock由於讀讀共享,且支援鎖降級,所及效率會高一些。由於它讀寫不共享,所以在讀寫高併發操作時,可能導致寫的操作鎖飢餓。

  是否可重入 公平性選擇 讀讀 讀寫 寫寫 鎖降級
ReentrantLock 互斥 互斥 互斥 不支援
ReentrantReadWriteLock 共享 互斥 互斥 支援

 

 

 



2 ReentrantLock示例

public class ReentrantLockTest1 {

    static ReentrantLock lo = new ReentrantLock();

    //讀讀共享  讀寫互斥 寫寫互斥
    public static void main(String[] args) {
        
        
for (int i = 0;i < 5;i++) { new Thread(() -> operate(), "要進行讀操作的執行緒" + i).start(); } for (int i = 0;i < 5;i++) { new Thread(() -> operate(), "要進行寫操作的執行緒執行緒" + i).start(); } } private static void operate() { lo.lock(); System.
out.println(Thread.currentThread().getName() + "開始操作-----------"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "結束操作-----------"); lo.unlock(); } }

執行結果,只有執行完一個操作,才能夠執行另一個操作,所有操作互斥

要進行讀操作的執行緒0開始操作-----------
要進行讀操作的執行緒0結束操作-----------
要進行讀操作的執行緒1開始操作-----------
要進行讀操作的執行緒1結束操作-----------
要進行讀操作的執行緒2開始操作-----------
要進行讀操作的執行緒2結束操作-----------
要進行讀操作的執行緒3開始操作-----------
要進行讀操作的執行緒3結束操作-----------
要進行讀操作的執行緒4開始操作-----------
要進行讀操作的執行緒4結束操作-----------
要進行寫操作的執行緒執行緒0開始操作-----------
要進行寫操作的執行緒執行緒0結束操作-----------
要進行寫操作的執行緒執行緒1開始操作-----------
要進行寫操作的執行緒執行緒1結束操作-----------
要進行寫操作的執行緒執行緒2開始操作-----------
要進行寫操作的執行緒執行緒2結束操作-----------
要進行寫操作的執行緒執行緒3開始操作-----------
要進行寫操作的執行緒執行緒3結束操作-----------
要進行寫操作的執行緒執行緒4開始操作-----------
要進行寫操作的執行緒執行緒4結束操作-----------

Process finished with exit code 0

 

3 ReentrantReadWriteLock 示例

  ReentrantReadWriteLock分為:

    讀鎖-ReentrantReadWriteLock.ReadLock

    寫鎖-ReentrantReadWriteLock.WriteLock

 

3.1 示例1

  下面示例演示出了:讀讀不互斥,讀寫互斥,寫寫互斥

public class ReentrantLockTest2 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();
    
    public static void main(String[] args) {


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read執行緒" + i).start();
        }
        
        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write執行緒" + i).start();
        }
    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始寫-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束寫-----------");
        writeLock.unlock();
    }
}

執行結果,發現多個讀操作可以同時進行,讀寫操作互斥,讀讀也互斥

read執行緒0開始讀-----------
read執行緒4開始讀-----------
read執行緒3開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒0結束讀-----------
read執行緒3結束讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒2結束讀-----------
write執行緒0開始寫-----------
write執行緒0結束寫-----------
write執行緒1開始寫-----------
write執行緒1結束寫-----------
write執行緒2開始寫-----------
write執行緒2結束寫-----------
write執行緒3開始寫-----------
write執行緒3結束寫-----------
write執行緒4開始寫-----------
write執行緒4結束寫-----------

Process finished with exit code 0

 

3.2 示例2

  這個示例是為了進一步演示讀寫互斥,和示例2相比,這裡for迴圈先呼叫寫,再呼叫的讀,發現讀寫,還是互斥

public class ReentrantLockTest3 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();

    public static void main(String[] args) {

        for (int i = 0;i < 5;i++) {
            new Thread(() -> write(), "write執行緒" + i).start();
        }

        for (int i = 0;i < 5;i++) {
            new Thread(() -> read(), "read執行緒" + i).start();
        }
    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始寫-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束寫-----------");
        writeLock.unlock();
    }
}

執行結果

write執行緒0開始寫-----------
write執行緒0結束寫-----------
write執行緒1開始寫-----------
write執行緒1結束寫-----------
write執行緒2開始寫-----------
write執行緒2結束寫-----------
write執行緒3開始寫-----------
write執行緒3結束寫-----------
write執行緒4開始寫-----------
write執行緒4結束寫-----------
read執行緒0開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒4開始讀-----------
read執行緒3開始讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒2結束讀-----------
read執行緒0結束讀-----------
read執行緒3結束讀-----------

Process finished with exit code 0

 

4 ReentrantReadWriteLock鎖降級

  簡單來說,就是一個執行緒再持有寫鎖,且還未釋放的時候,可以去獲取讀鎖,這樣子,該執行緒就可以同時持有讀寫鎖。

  多個執行緒操作一個變數a,使用ReentrantReadWriteLock執行緒1對a進行修改,值為100,它想要保證100這個值被其它所有的執行緒獲取到,該怎麼做?

  那麼我們要去寫的時候,先去獲取寫鎖(此時其它執行緒不能讀寫),寫完了,再獲取讀鎖(此時其它執行緒不能讀寫),此時同時持有讀寫鎖,然後釋放讀鎖,只持有寫鎖((此時其它執行緒不能寫,但是可以讀)),這個從讀寫變為寫鎖的過程,就叫做鎖降級。在持有讀鎖變為持有寫鎖的過程中,其它執行緒都不能寫,保證我寫的資料能夠被其它執行緒看到。如果不能同時持有讀寫鎖那麼就只能這麼操作,獲取寫鎖-寫-釋放寫鎖-獲取讀鎖-其它執行緒讀-釋放讀鎖,在釋放寫鎖和獲取讀鎖之間就會存在空隙,有可能被其它執行緒進行寫操作,導致它寫的結果不能被其他執行緒獲取。

 

 

 

 

 

   所以,鎖降級解決的就是即寫即讀的問題

 

5 鎖降級示例

5.1 示例1

  在釋放讀鎖後,釋放寫鎖前,其它執行緒可讀,且中間其它執行緒都不可寫

public class ReentrantLockTest5 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();

    public static void main(String[] args) {

        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write執行緒" + i).start();
        }

        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read執行緒" + i).start();
        }

    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始寫-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束寫結束寫-----------");

        readLock.lock();  //鎖降級 同時持有寫鎖和讀鎖
        System.out.println(Thread.currentThread().getName() + "鎖降級-----------");
        System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
        writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //此時,它只擁有讀鎖
        System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
        readLock.unlock();  //
    }
}

執行結果,可以看到write8在釋放了寫鎖後釋放讀鎖前,其它執行緒進來讀了

write執行緒1開始寫-----------
write執行緒1結束寫結束寫-----------
write執行緒1鎖降級-----------
write執行緒1釋放寫鎖-----------
write執行緒1釋放讀鎖-----------
write執行緒0開始寫-----------
write執行緒0結束寫結束寫-----------
write執行緒0鎖降級-----------
write執行緒0釋放寫鎖-----------
write執行緒0釋放讀鎖-----------
write執行緒2開始寫-----------
write執行緒2結束寫結束寫-----------
write執行緒2鎖降級-----------
write執行緒2釋放寫鎖-----------
write執行緒2釋放讀鎖-----------
write執行緒3開始寫-----------
write執行緒3結束寫結束寫-----------
write執行緒3鎖降級-----------
write執行緒3釋放寫鎖-----------
write執行緒3釋放讀鎖-----------
write執行緒4開始寫-----------
write執行緒4結束寫結束寫-----------
write執行緒4鎖降級-----------
write執行緒4釋放寫鎖-----------
write執行緒4釋放讀鎖-----------
write執行緒5開始寫-----------
write執行緒5結束寫結束寫-----------
write執行緒5鎖降級-----------
write執行緒5釋放寫鎖-----------
write執行緒5釋放讀鎖-----------
write執行緒7開始寫-----------
write執行緒7結束寫結束寫-----------
write執行緒7鎖降級-----------
write執行緒7釋放寫鎖-----------
write執行緒7釋放讀鎖-----------
write執行緒6開始寫-----------
write執行緒6結束寫結束寫-----------
write執行緒6鎖降級-----------
write執行緒6釋放寫鎖-----------
read執行緒1開始讀-----------
read執行緒1結束讀-----------
write執行緒6釋放讀鎖-----------
write執行緒9開始寫-----------
write執行緒9結束寫結束寫-----------
write執行緒9鎖降級-----------
write執行緒9釋放寫鎖-----------
read執行緒0開始讀-----------
read執行緒0結束讀-----------
write執行緒9釋放讀鎖-----------
write執行緒8開始寫-----------
write執行緒8結束寫結束寫-----------
write執行緒8鎖降級-----------
write執行緒8釋放寫鎖-----------
read執行緒7開始讀-----------
read執行緒3開始讀-----------
read執行緒5開始讀-----------
read執行緒4開始讀-----------
read執行緒8開始讀-----------
read執行緒9開始讀-----------
read執行緒2開始讀-----------
read執行緒6開始讀-----------
read執行緒9結束讀-----------
read執行緒7結束讀-----------
read執行緒3結束讀-----------
read執行緒4結束讀-----------
read執行緒6結束讀-----------
read執行緒2結束讀-----------
read執行緒8結束讀-----------
read執行緒5結束讀-----------
write執行緒8釋放讀鎖-----------

Process finished with exit code 0

 

5.2 示例2

  在釋放讀鎖前其它執行緒不可讀寫

 

 

public class ReentrantLockTest4 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    public static void main(String[] args) {


        for (int i = 0;i < 10;i++) {
            new Thread(() -> write(), "write執行緒" + i).start();
        }

        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(), "read執行緒" + i).start();
        }

    }

    private static void read() {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
    }

    private static void write() {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始寫-----------");
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "結束寫結束寫-----------");

        readLock.lock();  //鎖降級  同時持有寫鎖和讀鎖
        System.out.println(Thread.currentThread().getName() + "鎖降級-----------");
        try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
        writeLock.unlock(); //釋放寫鎖-此時值持有讀鎖
        System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
        readLock.unlock();  //釋放讀鎖
    }
}

執行結果

write執行緒1開始寫-----------
write執行緒1結束寫結束寫-----------
write執行緒1鎖降級-----------
write執行緒1釋放寫鎖-----------
write執行緒1釋放讀鎖-----------
write執行緒2開始寫-----------
write執行緒2結束寫結束寫-----------
write執行緒2鎖降級-----------
write執行緒2釋放寫鎖-----------
write執行緒2釋放讀鎖-----------
write執行緒0開始寫-----------
write執行緒0結束寫結束寫-----------
write執行緒0鎖降級-----------
write執行緒0釋放寫鎖-----------
write執行緒0釋放讀鎖-----------
write執行緒3開始寫-----------
write執行緒3結束寫結束寫-----------
write執行緒3鎖降級-----------
write執行緒3釋放寫鎖-----------
write執行緒3釋放讀鎖-----------
write執行緒4開始寫-----------
write執行緒4結束寫結束寫-----------
write執行緒4鎖降級-----------
write執行緒4釋放寫鎖-----------
write執行緒4釋放讀鎖-----------
write執行緒7開始寫-----------
write執行緒7結束寫結束寫-----------
write執行緒7鎖降級-----------
write執行緒7釋放寫鎖-----------
write執行緒7釋放讀鎖-----------
write執行緒8開始寫-----------
write執行緒8結束寫結束寫-----------
write執行緒8鎖降級-----------
write執行緒8釋放寫鎖-----------
write執行緒8釋放讀鎖-----------
write執行緒5開始寫-----------
write執行緒5結束寫結束寫-----------
write執行緒5鎖降級-----------
write執行緒5釋放寫鎖-----------
write執行緒5釋放讀鎖-----------
write執行緒6開始寫-----------
write執行緒6結束寫結束寫-----------
write執行緒6鎖降級-----------
write執行緒6釋放寫鎖-----------
write執行緒6釋放讀鎖-----------
write執行緒9開始寫-----------
write執行緒9結束寫結束寫-----------
write執行緒9鎖降級-----------
write執行緒9釋放寫鎖-----------
write執行緒9釋放讀鎖-----------
read執行緒0開始讀-----------
read執行緒1開始讀-----------
read執行緒2開始讀-----------
read執行緒3開始讀-----------
read執行緒4開始讀-----------
read執行緒5開始讀-----------
read執行緒6開始讀-----------
read執行緒7開始讀-----------
read執行緒8開始讀-----------
read執行緒9開始讀-----------
read執行緒4結束讀-----------
read執行緒1結束讀-----------
read執行緒0結束讀-----------
read執行緒2結束讀-----------
read執行緒5結束讀-----------
read執行緒3結束讀-----------
read執行緒6結束讀-----------
read執行緒9結束讀-----------
read執行緒7結束讀-----------
read執行緒8結束讀-----------

 

5.3 再來個更清晰的例子

  持有讀寫鎖,釋放寫鎖後釋放讀鎖前,其它執行緒可讀

public class ReentrantLockTest8 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    //讀讀共享  讀寫互斥 寫寫互斥
    public static void main(String[] args) {

        CountDownLatch c = new CountDownLatch(10);

        new Thread(() ->write(c), "write執行緒" ).start();


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(c), "read執行緒" + i).start();
        }

    }

    private static void read(CountDownLatch c) {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
        c.countDown();
    }

    private static void write(CountDownLatch c) {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "寫-----------");
        readLock.lock();  //鎖降級 同時持有寫鎖和讀鎖


        System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
        writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀

        try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
        readLock.unlock();  //釋放讀鎖
    }
}

執行結果,執行完成

write執行緒寫-----------
write執行緒釋放寫鎖-----------
read執行緒0開始讀-----------
read執行緒5開始讀-----------
read執行緒4開始讀-----------
read執行緒4結束讀-----------
read執行緒1開始讀-----------
read執行緒1結束讀-----------
read執行緒7開始讀-----------
read執行緒7結束讀-----------
read執行緒6開始讀-----------
read執行緒5結束讀-----------
read執行緒3開始讀-----------
read執行緒9開始讀-----------
read執行緒9結束讀-----------
read執行緒2開始讀-----------
read執行緒0結束讀-----------
read執行緒2結束讀-----------
read執行緒3結束讀-----------
read執行緒6結束讀-----------
read執行緒8開始讀-----------
read執行緒8結束讀-----------
write執行緒釋放讀鎖-----------

Process finished with exit code 0

 

持有讀寫鎖,釋放讀鎖和寫鎖前,其它執行緒不可讀

try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }放到釋放寫鎖前

public class ReentrantLockTest7 {

    static ReentrantReadWriteLock lo = new ReentrantReadWriteLock();
    static ReentrantReadWriteLock.ReadLock readLock = lo.readLock();
    static ReentrantReadWriteLock.WriteLock writeLock = lo.writeLock();


    //讀讀共享  讀寫互斥 寫寫互斥
    public static void main(String[] args) {

        CountDownLatch c = new CountDownLatch(10);

        new Thread(() ->write(c), "write執行緒" ).start();


        for (int i = 0;i < 10;i++) {
            new Thread(() -> read(c), "read執行緒" + i).start();
        }

    }

    private static void read(CountDownLatch c) {
        readLock.lock();
        System.out.println(Thread.currentThread().getName() + "開始讀-----------");
        System.out.println(Thread.currentThread().getName() + "結束讀-----------");
        readLock.unlock();
        c.countDown();
    }

    private static void write(CountDownLatch c) {
        writeLock.lock();
        System.out.println(Thread.currentThread().getName() + "寫-----------");
        readLock.lock();  //鎖降級 同時持有寫鎖和讀鎖

        try { c.await(); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println(Thread.currentThread().getName() + "釋放寫鎖-----------");
        writeLock.unlock(); //釋放寫鎖,只持有讀鎖,此時其它執行緒可讀

        System.out.println(Thread.currentThread().getName() + "釋放讀鎖-----------");
        readLock.unlock();  //釋放讀鎖
        
    }
}

執行結果,卡住了,沒有執行完