我们提供安全,免费的手游软件下载!

安卓手机游戏下载_安卓手机软件下载_安卓手机应用免费下载-先锋下载

当前位置: 主页 > 软件教程 > 软件教程

深入了解Java中的读写锁:ReentrantReadWriteLock

来源:网络 更新时间:2024-04-28 09:32:06

最近我对java.util.concurrent.locks包下的同步类进行了深入研究,这个包被称为并发编程的基石,其中的concurrent包中充满了精品。今天我们将继续探讨这个话题,从一个常见的Java面试题开始:

小伙子,来说一说Java中的读写锁,你都用过哪些读写锁吧?

这个问题该如何回答呢?让我们从ReentrantReadWriteLock开始说起,这个类是今天的主角,它同样来自于java.util.concurrent.locks包。

读写锁的背景

在过去的学习中,我们了解了synchronized和ReentrantLock这种独占式锁,它们的好处是确保线程安全,但缺点是同一时刻只能有一个线程持有锁,这严重影响了效率。另外,我们还学习了Semaphore(信号量),虽然它支持多个线程同时获取,但无法很好地保障线程安全性。我们需要一种既高效又安全的同步锁。

考虑到在实际生产环境中,数据的读取要比写入更为频繁。因此,聪明的开发者们将读数据时设置为共享锁,支持多个线程持有读锁;而在写入时,考虑到线程安全,采用独占锁,同一时刻仅允许一个线程持有写锁。在这种背景下,读写锁应运而生!

读写锁:ReentrantReadWriteLock

ReentrantReadWriteLock是ReadWriteLock接口的默认实现类。从名字可以看出,它也是一种具有可重入性的锁,同时也支持公平与非公平的配置。它底层有两把锁,一把是WriteLock(写锁),一把是ReadLock(读锁)。读锁是共享锁,写锁是独占锁。读锁可以被同时读,可以同时被多个线程持有,而写锁最多只能同时被一个线程持有。它也是基于AQS实现的底层锁获取与释放逻辑。

内部构造

根据上面的构造图,如果还没有搞清楚ReentrantReadWriteLock的底层构造的话,那我们跟入源码中取一探究竟吧!

【源码分析】

// 内部结构
private final ReentrantReadWriteLock.ReadLock readerLock;
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/*1、用以继承AQS,获得AOS的特性,以及AQS的钩子函数*/
abstract static class Sync extends AbstractQueuedSynchronizer {
    // 具体实现
}
/*非公平模式,默认为这种模式*/
static final class NonfairSync extends Sync {
    // 具体实现
}
/*公平模式,通过构造方法参数设置*/
static final class FairSync extends Sync {
    // 具体实现
}
/*读锁,底层是共享锁*/
public static class ReadLock implements Lock, java.io.Serializable {
    private final Sync sync;
    protected ReadLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
    }
    // 具体实现
}
/*写锁,底层是独占锁*/
public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;
    protected WriteLock(ReentrantReadWriteLock lock) {
            sync = lock.sync;
    }
    // 具体实现
}

// 构造方法,初始化两个锁
public ReentrantReadWriteLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
    readerLock = new ReadLock(this);
    writerLock = new WriteLock(this);
}

// 获取读锁和写锁的方法
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

上面为底层的主要构造内容,ReentrantReadWriteLock中共写了5个静态内部类,各有功效,在上面的注释中也有提及。

使用案例

那么这个读写锁如何使用呢?我们写一个小小的测试案例,也感受一下。

【测试案例】

public class Test {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private int data = 0;

    /**
     * 写方法
     * @param value
     */
    public void write(int value) {
        //注意,获取锁的操作要在try/finally外面
        lock.writeLock().lock(); // 获取写锁
        try {
            data = value;
            System.out.println("线程:"+Thread.currentThread().getName() + "写" + data);
        } finally {
            lock.writeLock().unlock(); // 释放写锁
        }
    }

    public void read() {
        lock.readLock().lock(); // 获取读锁
        try {
            System.out.println("线程:" + Thread.currentThread().getName() + "读" + data);
        } finally {
            lock.readLock().unlock(); // 释放读锁
        }
    }

    public static void main(String[] args) {
        Test test = new Test();
        // 创建读线程
        Thread readThread1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                test.read();
            }
        });

        Thread readThread2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                test.read();
            }
        });

        // 创建写线程
        Thread writeThread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                test.write(i);
            }
        });

        readThread1.start();
        readThread2.start();
        writeThread.start();

        try {
            readThread1.join();
            readThread2.join();
            writeThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

输出:

线程:Thread-1读0
线程:Thread-0读0
线程:Thread-1读0
线程:Thread-2写0
线程:Thread-2写1
线程:Thread-2写2
线程:Thread-2写3
线程:Thread-0读3
线程:Thread-1读3
线程:Thread-2写4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4
线程:Thread-1读4
线程:Thread-0读4

通过输出内容,我们进一步得证,在ReentrantReadWriteLock在使用读锁时,可以支持多个线程获取读资源,而在调用写锁时,其他读线程和写线程均阻塞等待当前线程写完。

存在的问题

虽然ReentrantReadWriteLock优化了原有的独占锁对于程序读写的性能,但它仍然存在一个弊端,就是 “写饥饿” ,因为在写的时候,是独占模式,其他线程不能读也不能写,这时候若有大量的读操作的话,那这些线程也只能等待着,从而带来写饥饿。

那这个问题怎么解决呢?我们在下一篇StampedLock(锁王)的讲解中,进行解答哈,敬请期待!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得 留言+点赞+收藏 呀。原创不易,转载请联系Build哥!

如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!