ReentrantReadWriteLock。
两个线程的读操作是不涉及到修改的,我们可以读读的话是不加锁的。
锁的升级和降级:https://blog.csdn.net/aitangyong/article/details/38315885。
重入的话锁是可以降级不可以升级的。
我们来演示下读写锁的使用方法。
不管是什么锁,锁的本质是在方法里面加锁,而不是在线程里面加锁的。
代码:
package cn.itcast.n8;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import static cn.itcast.n2.util.Sleeper.sleep;
@Slf4j(topic = "c.TestReadWriteLock")
public class TestReadWriteLock {
public static void main(String[] args) throws InterruptedException {
DataContainer dataContainer = new DataContainer();
new Thread(() -> {
dataContainer.read();
}, "t1").start();
new Thread(() -> {
dataContainer.read();
}, "t2").start();
}
}
@Slf4j(topic = "c.DataContainer")
class DataContainer {
private Object data;
private ReentrantReadWriteLock rw = new ReentrantReadWriteLock();
private ReentrantReadWriteLock.ReadLock r = rw.readLock();
private ReentrantReadWriteLock.WriteLock w = rw.writeLock();
public Object read() {
System.out.println("get readlock...");
r.lock();
try {
System.out.println("read");
sleep(1);
return data;
} finally {
System.out.println("relese read lock...");
r.unlock();
}
}
public void write() {
System.out.println("get writelock...");
w.lock();
try {
System.out.println("write");
sleep(1);
} finally {
System.out.println("release writelock...");
w.unlock();
}
}
}
---247---
读锁是不支持条件变量的,写锁是支持条件变量的。
重入的话是不支持升级的。
我们看下jdk自己带的锁的例子:
缓存在释放写锁的时候降级为读锁,这样别的线程就不能加写锁,可以让缓存快速的读取。
---248---
读写锁的应用----应用到缓存,保证缓存和数据库的一致性-----------------牛逼啊。
代码:
装饰器模式:https://www.runoob.com/design-pattern/decorator-pattern.html
我们只看重点的代码:最开始是这样写的。
这个是有问题的。
---249---
读取的时候就是读的是缓存缓存没有就读数据库再把数据放在缓存。
更新就是先清缓存,再更新数据库。
找问题:
我们想一下多线程的并发的问题。
清空缓存和更新数据库不加锁,导致不一致问题。
---
总结:先更新数据库,再清除缓存是合适的。
增加删除只是清除缓存 不更新缓存数据 查询更新缓存数据
强一致:加锁实现的。
总结:集合类不好 并发大所有的线程读取都会查数据库 先更新库再清空缓存
---250---
读写锁:既保证一致性,又不至于性能很低。
写操作加写锁,读操作加读锁,锁是加在方法上的,方法是线程调用的,下面找个缓存和数据库先后顺序就不重要了。
·
写加写锁。
读锁是如何加呢?
这里再做一次判断:
为什么?多个写阻塞了,释放之后就会再进来的。这个时候已经跳过判断了。
其实性能肯定有影响的。
---251---
补充:读多写少,查询多呢?
锁的细粒度,比如两个表读写不要用一把锁。
---252---