原来ReadWriteLock也能开发高性能缓存,看完我也能和面试官好好聊聊了!(1)

文章介绍了Java中两种缓存加载方式——全量加载和按需加载,以及ReentrantReadWriteLock在处理读写锁和数据同步问题的应用。重点讨论了在高并发场景下的锁降级策略,以提高系统性能。
摘要由CSDN通过智能技术生成

在以往的经验中,有两种向缓存中加载数据的方式,一种是:项目启动时,将数据全量加载到缓存中,一种是在项目运行期间,按需加载所需要的缓存数据。

在这里插入图片描述

接下来,我们就分别来看看全量加载缓存和按需加载缓存的方式。

全量加载缓存


全量加载缓存相对来说比较简单,就是在项目启动的时候,将数据一次性加载到缓存中,这种情况适用于缓存数据量不大,数据变动不频繁的场景,例如:可以缓存一些系统中的数据字典等信息。整个缓存加载的大体流程如下所示。

在这里插入图片描述

将数据全量加载到缓存后,后续就可以直接从缓存中读取相应的数据了。

全量加载缓存的代码实现比较简单,这里,我就直接使用如下代码进行演示。

public class ReadWriteLockCache<K,V> {

private final Map<K, V> m = new HashMap<>();

private final ReadWriteLock rwl = new ReentrantReadWriteLock();

// 读锁

private final Lock r = rwl.readLock();

// 写锁

private final Lock w = rwl.writeLock();

public ReadWriteLockCache(){

//查询数据库

List<Field<K, V>> list = …;

if(!CollectionUtils.isEmpty(list)){

list.parallelStream().forEach((f) ->{

m.put(f.getK(), f.getV);

});

}

}

// 读缓存

public V get(K key) {

r.lock();

try { return m.get(key); }

finally { r.unlock(); }

}

// 写缓存

public V put(K key, V value) {

w.lock();

try { return m.put(key, value); }

finally { w.unlock(); }

}

}

按需加载缓存


按需加载缓存也可以叫作懒加载,就是说:需要加载的时候才会将数据加载到缓存。具体来说:就是程序启动的时候,不会将数据加载到缓存,当运行时,需要查询某些数据,首先检测缓存中是否存在需要的数据,如果存在,则直接读取缓存中的数据,如果不存在,则到数据库中查询数据,并将数据写入缓存。后续的读取操作,因为缓存中已经存在了相应的数据,直接返回缓存的数据即可。

在这里插入图片描述

这种查询缓存的方式适用于大多数缓存数据的场景。

我们可以使用如下代码来表示按需查询缓存的业务。

class ReadWriteLockCache<K,V> {

private final Map<K, V> m = new HashMap<>();

private final ReadWriteLock rwl = new ReentrantReadWriteLock();

private final Lock r = rwl.readLock();

private final Lock w = rwl.writeLock();

V get(K key) {

V v = null;

//读缓存

r.lock();

try {

v = m.get(key);

} finally{

r.unlock();

}

//缓存中存在,返回

if(v != null) {

return v;

}

//缓存中不存在,查询数据库

w.lock();

try {

//再次验证缓存中是否存在数据

v = m.get(key);

if(v == null){

//查询数据库

v=从数据库中查询出来的数据

m.put(key, v);

}

} finally{

w.unlock();

}

return v;

}

}

这里,在get()方法中,首先从缓存中读取数据,此时,我们对查询缓存的操作添加了读锁,查询返回后,进行解锁操作。判断缓存中返回的数据是否为空,不为空,则直接返回数据;如果为空,则获取写锁,之后再次从缓存中读取数据,如果缓存中不存在数据,则查询数据库,将结果数据写入缓存,释放写锁。最终返回结果数据。

这里,有小伙伴可能会问:为啥程序都已经添加写锁了,在写锁内部为啥还要查询一次缓存呢?

这是因为在高并发的场景下,可能会存在多个线程来竞争写锁的现象。例如:第一次执行get()方法时,缓存中的数据为空。如果此时有三个线程同时调用get()方法,同时运行到 w.lock()代码处,由于写锁的排他性。此时只有一个线程会获取到写锁,其他两个线程则阻塞在w.lock()处。获取到写锁的线程继续往下执行查询数据库,将数据写入缓存,之后释放写锁。

此时,另外两个线程竞争写锁,某个线程会获取到锁,继续往下执行,如果在w.lock()后没有v = m.get(key); 再次查询缓存的数据,则这个线程会直接查询数据库,将数据写入缓存后释放写锁。最后一个线程同样会按照这个流程执行。

这里,实际上第一个线程已经查询过数据库,并且将数据写入缓存了,其他两个线程就没必要再次查询数据库了,直接从缓存中查询出相应的数据即可。所以,在w.lock()后添加v = m.get(key); 再次查询缓存的数据,能够有效的减少高并发场景下重复查询数据库的问题,提升系统的性能。

读写锁的升降级


关于锁的升降级,小伙伴们需要注意的是:在ReadWriteLock中,锁是不支持升级的,因为读锁还未释放时,此时获取写锁,就会导致写锁永久等待,相应的线程也会被阻塞而无法唤醒。

虽然不支持锁升级,但是ReadWriteLock支持锁降级,例如,我们来看看官方的ReentrantReadWriteLock示例,如下所示。

class CachedData {

Object data;

volatile boolean cacheValid;

final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

void processCachedData() {

rwl.readLock().lock();

if (!cacheValid) {

// Must release read lock before acquiring write lock

rwl.readLock().unlock();

rwl.writeLock().lock();

try {

// Recheck state because another thread might have

// acquired write lock and changed state before we did.

if (!cacheValid) {

data = …

cacheValid = true;

}

// Downgrade by acquiring read lock before releasing write lock

rwl.readLock().lock();

} finally {

rwl.writeLock().unlock(); // Unlock write, still hold read

}

}

try {

use(data);

} finally {

rwl.readLock().unlock();

}

}

}}

数据同步问题

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

image

image

data);

} finally {

rwl.readLock().unlock();

}

}

}}

数据同步问题

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

[外链图片转存中…(img-3NJHozmh-1714639679951)]

[外链图片转存中…(img-d1cu19XE-1714639679952)]

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

  • 27
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值