“你好,我们公司对你的简历很感兴趣,能不能约个时间聊一聊?”
小米刚吃完晚饭,就接到了猎头的电话。作为一个有 5 年 Java 开发经验的工程师,小米已经经历过无数次面试,但每次社招面试,总有一些刁钻的问题让人措手不及。
面试官的问题一向都是从基础到深入,一步步挖掘你的掌握程度。果然,这次面试官刚热身完 JVM、集合、多线程,就抛出了一个让小米眼前一亮的问题:
“你能讲讲 ReadWriteLock 是什么吗?”
小米微微一笑,果然来了!
ReadWriteLock 初识:一把高效的锁要回答这个问题,得先看看 ReadWriteLock 是什么。
在 Java 的并发编程中,我们经常使用 synchronized 或 ReentrantLock 来保证线程安全,但这些锁在高并发环境下有个问题——“读多写少”时,性能下降得厉害!
举个例子,你在图书馆借书的时候,管理员一次只能接待一个人,不管你是还书(写操作)还是借书(读操作),其他人都得排队等候。这种机制会导致大量读操作的线程被写操作阻塞,性能很差。
有没有办法优化呢?
当然有!Java 在 java.util.concurrent.locks 包里提供了 ReadWriteLock,它的核心思想是:
多个线程可以同时读,但写时必须独占
写操作会阻塞所有的读操作
这样,在读多写少的场景下,就可以大幅提高并发能力!
ReadWriteLock 的实现:ReentrantReadWriteLockJava 标准库中提供了 ReadWriteLock 的实现——ReentrantReadWriteLock。
1. 结构分析
ReentrantReadWriteLock 里有两个重要的锁:
读锁(ReadLock):允许多个线程同时读取数据。
写锁(WriteLock):只有一个线程能修改数据,且写锁会阻塞所有的读锁和其他写锁。
2. 基本使用
假设有一个共享变量 data,我们用 ReentrantReadWriteLock 来保护它。
3. 读写并发测试
我们可以启动多个线程,模拟多个读线程和少量写线程的情况。
4. 运行结果(示例)
可以看到:多个读线程可以同时读取数据,而写线程需要等待,只有写锁释放后,读操作才能继续进行。
ReadWriteLock 的特点和应用场景1. ReadWriteLock 适用于哪些场景?
缓存系统:读操作多,写操作少,比如读取配置文件、热点数据查询等。
文件系统:多个线程可以同时读取文件,但写入时必须独占。
排行榜:读取排行榜数据的用户很多,更新排行榜的操作相对较少。
2. ReadWriteLock vs synchronized vs ReentrantLock
如果面试官对 ReadWriteLock 感兴趣,可能会继续深入问你:
1、ReadWriteLock 会导致“写饥饿”问题吗?
默认情况下,ReentrantReadWriteLock 是非公平锁,写线程可能会被读线程“饿死”。
可以使用公平模式:
这样写线程会有更高的优先级,减少饥饿问题。
2、ReadWriteLock 适用于 CPU 密集型任务吗?
不适合!它适用于IO 密集型任务,比如读取数据库、文件等。
对于 CPU 密集型任务,ReentrantLock 可能是更好的选择。
3、ReadWriteLock 的底层实现原理?
ReentrantReadWriteLock 内部维护了一个共享读锁和独占写锁,用 AbstractQueuedSynchronizer (AQS) 实现线程同步。
读写状态是通过一个 state 变量管理的,高 16 位记录读锁数,低 16 位记录写锁数。
总结这次面试让小米又加深了对 ReadWriteLock 的理解,总结如下:
ReadWriteLock 适用于读多写少的场景,读操作可以并发执行,写操作需要独占。
ReentrantReadWriteLock 是 ReadWriteLock 的默认实现,内部维护了读锁和写锁。
需要注意写饥饿问题,可以使用公平锁来优化。
底层基于 AQS,实现了共享读锁和独占写锁的机制。
END面试结束,面试官点头微笑,小米也松了一口气,心想:这道题,稳了!
我是小米,一个喜欢分享技术的31岁程序员。如果你喜欢我的文章,欢迎关注我的微信公众号“软件求生”,获取更多技术干货!