Skip to content

读写锁

1. 读写锁介绍

1.1 读写锁的演进

Alt text

1.2 读写锁场景

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA的并发包提供了读写锁ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。

1.3 读写锁条件和特点

  1. 线程进入读锁的前提条件:
    • 没有其他线程的写锁
    • 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
  2. 线程进入写锁的前提条件:
    • 没有其他线程的读锁
    • 没有其他线程的写锁

总结:一个资源可以被多个读线程访问,或者可以被一个写线程访问,但是不能同时存在读写线程,读写互斥,读读共享
而读写锁有以下三个重要的特性:

  • 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
  • 重进入:读锁和写锁都支持线程重进入。
  • 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

2. ReadWriteLock接口

java
public interface ReadWriteLock {

    Lock readLock();

    Lock writeLock();
}

ReadWriteLock接口提供了两个方法:一个用来获取读锁,一个用来获取写锁。ReentrantReadWriteLock实现了ReadWriteLock接口。

java
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

public static void main(String[] args) {
    final Demo_ReentrantReadWriteLock test = new Demo_ReentrantReadWriteLock();

    new Thread() {
        public void run() {
            test.get(Thread.currentThread());
        }
    }.start();

    new Thread() {
        public void run() {
            test.get(Thread.currentThread());
        }
    }.start();
}

public void get(Thread thread) {
    rwl.readLock().lock();
    try{
        long start = System.currentTimeMillis();
        while(System.currentTimeMillis() - start <= 1) {
            System.out.println(thread.getName()+"正在进行读操作");
        }
        System.out.println(thread.getName()+"读操作完毕");
    }finally {
        rwl.readLock().unlock();
    }
}

运行结果:
Alt text 从运行结果看出使用读写锁可以让多个线程同时进行读操作, 这样就大大提升了读操作的效率。

提示

  • 如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。
  • 如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

3. 锁降级

锁降级可以保证数据的一致性:通过先获取写锁再降级为读锁,可以确保数据在修改期间不被其他线程读取,保证了数据的一致性。
锁降级可以提高性能:在一些场景下,持有写锁期间需要多次读取数据比较耗时,可以先获取写锁,然后降级为读锁,通过锁降级可以减少锁的竞争,提高系统的并发性能。 Alt text

java
public static void main(String[] args) throws ExecutionException, InterruptedException {
    ReentrantReadWriteLock rrl = new ReentrantReadWriteLock();
    ReentrantReadWriteLock.ReadLock readLock = rrl.readLock();
    ReentrantReadWriteLock.WriteLock writeLock = rrl.writeLock();

    FutureTask<String> futureTask = new FutureTask<>(() -> {
        //  获取写锁
        writeLock.lock();
        System.out.println("获取写锁===================");
        // 继续获取读锁, 写锁本身所在线程可以获取读锁
        readLock.lock();
        System.out.println("读取数据===================");
        // 锁降级,释放写锁
        writeLock.unlock();

        readLock.unlock();
        return "success";
    });
    new Thread(futureTask, "demo").start();

    futureTask.get();
}

运行结果:
Alt text