Java多執行緒程式設計筆記9:ReentrantReadWriteLock

weixin_33935777發表於2018-12-13

ReentrantLock具有完全互斥排他的效果,也即同一時間只有一個執行緒在執行ReentrantLock.lock()方法後邊的任務。這樣做保證了例項變數的執行緒安全性,但是效率低下。

ReentrantReadWriteLock類是ReadWriteLock介面的實現類,這是一個讀寫鎖。讀寫鎖維護了兩個鎖,一個是讀相關的,也稱之為共享鎖;另外一個是寫相關的,也叫排他鎖。多個讀鎖之間不排斥,讀鎖與寫鎖互斥,寫鎖與寫鎖互斥。具體而言,就是多個執行緒可以同時進行讀取操作,但是同一時刻只允許一個執行緒進行寫入操作。

ReentarntReadWriteLock特性

  1. 公平性:預設是公平鎖,同時支援非公平鎖,吞吐量上來看是非公平優於公平
  2. 重入:支援重進入。讀執行緒在獲取讀鎖後,能再次獲取讀鎖。寫執行緒獲取了寫鎖後能再次獲取寫鎖,也能同時獲取讀鎖。
  3. 鎖降級:遵循獲取寫鎖、獲取讀鎖再釋放寫鎖的次序,寫鎖能降級成為讀鎖

ReentarntReadWriteLock的使用

讀讀共享示例程式碼:

class Service{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("獲得讀鎖 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(50000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
}

class MyThread extends Thread{
    private Service service;
    public MyThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service=new Service();
        MyThread a=new MyThread(service);
        a.setName("A");
        MyThread b=new MyThread(service);
        b.setName("B");
        a.start();
        b.start();
    }
}
複製程式碼

執行結果:

獲得讀鎖 B Thu Dec 13 16:09:29 CST 2018
獲得讀鎖 A Thu Dec 13 16:09:29 CST 2018
複製程式碼

可以發現,二者是近乎同時進行讀取的,也就是讀讀是共享的。也就是說允許了多個執行緒同時執行lock()方法後面的程式碼。

對於寫存在的情況,修改程式碼如下:

class Service{
    private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
    public void read(){
        try {
            lock.readLock().lock();
            System.out.println("獲得讀鎖 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public void write(){
        try {
            lock.writeLock().lock();
            System.out.println("獲得寫鎖 "+Thread.currentThread().getName()+" "+new Date(System.currentTimeMillis()));
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
}

class MyReadThread extends Thread{
    private Service service;
    public MyReadThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.read();
    }
}

class MyWriteThread extends Thread{
    private Service service;
    public MyWriteThread(Service service){
        this.service=service;
    }

    @Override
    public void run() {
        service.write();
    }
}

public class Run {
    public static void main(String[] args) {
        Service service=new Service();
        MyReadThread read1=new MyReadThread(service);
        read1.setName("read1");
        MyReadThread read2=new MyReadThread(service);
        read2.setName("read2");
        MyWriteThread write1=new MyWriteThread(service);
        write1.setName("write1");
        MyWriteThread write2=new MyWriteThread(service);
        write2.setName("write2");
        read1.start();
        write1.start();
        write2.start();
        read2.start();
    }
}
複製程式碼

執行結果:

獲得寫鎖 write1 Thu Dec 13 16:16:24 CST 2018
獲得讀鎖 read1 Thu Dec 13 16:16:29 CST 2018
獲得寫鎖 write2 Thu Dec 13 16:16:34 CST 2018
獲得讀鎖 read2 Thu Dec 13 16:16:39 CST 2018
複製程式碼

可以看到,四個執行緒是互斥的執行的,每個執行緒都是等待了5秒才執行。也就是說,只要出現寫操作的過程就是互斥的。

參考資料

相關文章