solitaryclown

ReentrantReadWriteLock

2022-01-01
solitaryclown
 

1. ReentrantReadWriteLock

1.1. 例子

package com.huangbei.test2;


import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.huangbei.util.Sleeper.sleep;

@Slf4j(topic = "c.Test-ReadWriteLock")
public class Test18 {
    public static void main(String[] args) {
        Data data = new Data();
        //1.两个线程读
        /*new Thread(() -> {
            data.read();
        }).start();
        new Thread(() -> {
            data.read();
        }).start();*/
        //2.两个线程一个读,一个写

        /*new Thread(() -> {
            data.write();
        }).start();
        new Thread(() -> {
            data.read();
        }).start();*/

        //3.两个线程写
        new Thread(() -> {
            data.write();
        }).start();
        new Thread(() -> {
            data.write();
        }).start();

    }
}

@Slf4j(topic = "c.Data")
class Data {
    private int data;
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock r = readWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock w = readWriteLock.writeLock();

    public int read() {
        r.lock();
        log.debug("获取到读锁");
        try {
            log.debug("读取数据...");
            sleep(1);
            log.debug("读取完毕,返回.");
            return data;
        } finally {
            r.unlock();
        }
    }

    public void write() {
        w.lock();
        log.debug("获取到写锁");
        try {
            log.debug("写入数据...");
            sleep(1);
            data = 0;
            log.debug("写入完毕,返回.");
        } finally {
            w.unlock();
        }
    }
}

结果:

第1种情况两个线程读读读共享两个线程能同时获得读锁
18:17:45.335 [Thread-1] c.Data - 获取到读锁
18:17:45.341 [Thread-1] c.Data - 读取数据...
18:17:45.335 [Thread-0] c.Data - 获取到读锁
18:17:45.342 [Thread-0] c.Data - 读取数据...
18:17:46.346 [Thread-0] c.Data - 读取完毕返回.
18:17:46.349 [Thread-1] c.Data - 读取完毕返回.

第2种情况一个线程读一个线程写读写互斥获取写锁必须等读锁释放反之亦然
18:19:48.308 [Thread-0] c.Data - 获取到写锁
18:19:48.313 [Thread-0] c.Data - 写入数据...
18:19:49.334 [Thread-0] c.Data - 写入完毕返回.
18:19:49.334 [Thread-1] c.Data - 获取到读锁
18:19:49.334 [Thread-1] c.Data - 读取数据...
18:19:50.341 [Thread-1] c.Data - 读取完毕返回.

第3种情况两个线程写写写互斥
18:20:23.241 [Thread-0] c.Data - 获取到写锁
18:20:23.249 [Thread-0] c.Data - 写入数据...
18:20:24.264 [Thread-0] c.Data - 写入完毕返回.
18:20:24.264 [Thread-1] c.Data - 获取到写锁
18:20:24.265 [Thread-1] c.Data - 写入数据...
18:20:25.279 [Thread-1] c.Data - 写入完毕返回.

1.2. 继承关系

TIg5kT.png

1.3. 可重入

读写锁是可重入的,但由于是两种锁,所以重入的顺序有限制。

锁重入是对于同一个线程而言的

  • 获取读锁再获取写锁,这是不支持的。
  • 获取写锁再获取读锁,这是支持的。

1.3.1. 例子

package com.huangbei.test2;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static com.huangbei.util.Sleeper.sleep;

@Slf4j(topic = "c.Test-ReadWriteLock")
public class Test20 {
    public static void main(String[] args) {
        Data2 data = new Data2();

        //1. 先获取读锁,再获取写锁
/*
        new Thread(() -> {
            data.readAndWrite();
        }).start();
*/
        //2. 先获取写锁,再获取读锁
        new Thread(() -> {
            data.writeAndRead();
        }).start();


    }
}

@Slf4j(topic = "c.Data")
class Data2 {
    private int data;
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static ReentrantReadWriteLock.ReadLock r = readWriteLock.readLock();
    private static ReentrantReadWriteLock.WriteLock w = readWriteLock.writeLock();

    public int readAndWrite() {
        r.lock();
        log.debug("获取到读锁");
        try {
            log.debug("读取数据...");
            sleep(1);
            log.debug("读取完毕.");
            w.lock();
            log.debug("获取到写锁");
            try {
                log.debug("我要写入数据.");
                sleep(1);
            } finally {
                w.unlock();
            }
            return data;
        } finally {
            r.unlock();
        }

    }

    public void writeAndRead() {
        w.lock();
        log.debug("获取到写锁");
        try {
            log.debug("写入数据...");
            sleep(1);
            data = 0;
            log.debug("写入完毕.");
            r.lock();
            log.debug("获取到读锁");
            try {
                log.debug("我要读数据.");
                sleep(1);
                log.debug("读取完毕.");
            } finally {
                r.unlock();
            }

        } finally {
            w.unlock();
        }
    }
}

1.3.2. 结果

//1. 先获取读锁,再获取写锁,写锁lock会被阻塞
21:25:07.560 [Thread-0] c.Data - 获取到读锁
21:25:07.565 [Thread-0] c.Data - 读取数据...
21:25:08.566 [Thread-0] c.Data - 读取完毕.

//2. 先获取写锁,再获取读锁,可以。
21:26:22.759 [Thread-0] c.Data - 获取到写锁
21:26:22.763 [Thread-0] c.Data - 写入数据...
21:26:23.768 [Thread-0] c.Data - 写入完毕.
21:26:23.768 [Thread-0] c.Data - 获取到读锁
21:26:23.769 [Thread-0] c.Data - 我要读数据.
21:26:24.770 [Thread-0] c.Data - 读取完毕.

Process finished with exit code 0

1.4. 原理

读写锁本质是是使用的Sync对象,它里面有读锁和写锁的上锁和释放方法。 具体原理见ReentrantReadWriteLock源码。


下一篇 Semaphore

Comments

Content