ReentrantReadWriteLock读写锁的使用2
本文可作为传智播客《张孝祥-Java多线程与并发库高级应用》的学习笔记。
这一节我们做一个缓存系统。
在读本节前
请先阅读
ReentrantReadWriteLock读写锁的使用1
第一版
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
System.out.println("ss "+cd.getData2("ss"));
System.out.println("ss "+cd.getData2("ss"));
System.out.println("mm "+cd.getData2("mm"));
System.out.println("mm "+cd.getData2("mm"));
}
public Object getData2(String key){
Object o=cache.get(key);
if (o==null) { //标识1
System.out.println("第一次查 没有"+key);
o=Math.random(); //实际上从数据库中获得
cache.put(key, o);
}
return o;
}
}
运行结果:
第一次查 没有ss
ss 0.4045014284225158
ss 0.4045014284225158
第一次查 没有mm
mm 0.9994663041529088
mm 0.9994663041529088
似乎没有问题。
你觉得呢?
如果有三个线程同时第一次到了getData2()的标识1处(所查找的key也都一样),一检查o为null,然后就去查数据库。在这种情况下,就等于三个线程查同一个key,然后都去了数据库。
这显然是不合理的。
第二版 synchronized
最简单的办法
public synchronized Object getData2(String key){
//.....
}
第三版 锁
上一节我们提到了ReentrantLock可以替换synchronized,前者是一种更为面向对象的设计。那我们试试。
private Lock l=new ReentrantLock(); //l作为CacheDemo的成员变量
public Object getData3(String key) {
l.lock();
Object o=null;
try {
o = cache.get(key);
if (o == null) { // 标识1
System.out.println("第一次查 没有" + key);
o = Math.random(); // 实际上从数据库中获得
cache.put(key, o);
}
} finally {
l.unlock();
}
return o;
}
第三版可以吗?
可以个p。
为什么,自己想。
我们得使用ReentrantReadWriteLock,在同一个方法中既有读锁也有写锁。
首先我又一个问题:
对同一个线程,可以在加了读锁后,没有解开读锁前,再加写锁吗?
换句话说,下面的代码会停吗?
public class CacheDemo {
private Map<String, Object> cache = new HashMap<String, Object>();
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
cd.getData2();
}
public void getData2(){
rwl.readLock().lock();
System.out.println(1);
rwl.writeLock().lock();
System.out.println(2);
rwl.readLock().unlock();
System.out.println(3);
rwl.writeLock().unlock();
System.out.println(4);
}
}
亲自试一下,控制台输出1后,程序就不动了。
说明加写锁前,读锁得先解开。
第四版
即有读锁,又有写锁
public static void main(String[] args) {
CacheDemo cd = new CacheDemo();
System.out.println("ss "+cd.getData4("ss"));
System.out.println("ss "+cd.getData4("ss"));
System.out.println("mm "+cd.getData4("mm"));
System.out.println("mm "+cd.getData4("mm"));
}
public Object getData4(String key){
rwl.readLock().lock();
Object o=null;
try {
o=cache.get(key);
if (o==null) {
System.out.println("第一次查 没有"+key);
rwl.readLock().unlock(); //标识4
rwl.writeLock().lock();
try {
if(value==null){ //标识0
value = "aaaa";//实际调用 queryDB();
cache.put(key,value);
}
} finally {
rwl.writeLock().unlock();
}
rwl.readLock().lock(); //标识1
}
}finally {
rwl.readLock().unlock(); //标注2
}
return o;
}
结果
第一次查 没有ss
ss 0.5989899889645358
ss 0.5989899889645358
第一次查 没有mm
mm 0.8534424949014686
mm 0.8534424949014686
这个还有三个问题
1为什么在标识2处还得解锁。
这个答案在上一节已经提过。
2为什么在标识1出还得加锁?
如果标识1处不加锁,且程序一直正常执行,那么到标识2处,它解谁的锁?
3为什么标识0出还要检查一次?
如果同时又三个线程运行到标识4(查找相同的key),其中一个获得写锁,写入数据后,释放了写锁。此时另外两个线程还需要去数据库跑一趟么?
另外把标识1处的加读锁,放到finally的前面称之为降级锁。对降级锁,我目前也不太清楚。
官方文档中给了一个例子就是关于降级锁,如下
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();
}
}
}
感谢glt
ReentrantReadWriteLock读写锁的使用2的更多相关文章
- ReentrantReadWriteLock读写锁的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...
- 锁对象-Lock: 同步问题更完美的处理方式 (ReentrantReadWriteLock读写锁的使用/源码分析)
Lock是java.util.concurrent.locks包下的接口,Lock 实现提供了比使用synchronized 方法和语句可获得的更广泛的锁定操作,它能以更优雅的方式处理线程同步问题,我 ...
- Java并发包源码学习系列:ReentrantReadWriteLock读写锁解析
目录 ReadWriteLock读写锁概述 读写锁案例 ReentrantReadWriteLock架构总览 Sync重要字段及内部类表示 写锁的获取 void lock() boolean writ ...
- ReentrantReadWriteLock读写锁简单原理案例证明
ReentrantReadWriteLock存在原因? 我们知道List的实现类ArrayList,LinkedList都是非线程安全的,Vector类通过用synchronized修饰方法保证了Li ...
- ReentrantReadWriteLock读写锁详解
一.读写锁简介 现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁.在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源:但是如果一个线 ...
- java多线程:并发包中ReentrantReadWriteLock读写锁的锁降级模板
写锁降级为读锁,但读锁不可升级或降级为写锁. 锁降级是为了让当前线程感知到数据的变化. //读写锁 private ReentrantReadWriteLock lock=new ReentrantR ...
- java中ReentrantReadWriteLock读写锁的使用
Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象.两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象. 读写锁:分为读 ...
- java多线程:ReentrantReadWriteLock读写锁使用
Lock比传统的线程模型synchronized更多的面向对象的方式.锁和生活似,应该是一个对象.两个线程运行的代码片段要实现同步相互排斥的效果.它们必须用同一个Lock对象. 读写锁:分为读锁和写锁 ...
- ReentrantReadWriteLock读写锁的使用1
本文可作为传智播客<张孝祥-Java多线程与并发库高级应用>的学习笔记. 一个简单的例子 两个线程,一个不断打印a,一个不断打印b public class LockTest { publ ...
随机推荐
- 打开CMDLINE中的 ” earlyprink “ 参数
点击打开链接 解决问题的过程中,好文章推荐,都保存在火狐wilson_sq@qq.com记录中~~~~~~~~grep -r "earlyprintk" kernelkernel/ ...
- 【安卓开发】Android系统中Parcelable和Serializable的区别
http://greenrobot.me/devpost/android-parcelable-serializable/ 进行Android开发的时候,我们都知道不能将对象的引用传给Activiti ...
- SSL协议相关证书文件
密钥: 我理解是公钥+私钥的统称. 密钥对: 公钥(证书)和私钥成对存在.通信双方各持有自己的私钥和对方的公钥.自己的私钥需密切保护,而公钥是公开给对方的.在windows下,单独存在的公钥一般是后缀 ...
- 小小聊天室 Python实现
相对于Java方式的聊天室,Python同样可以做得到.而且可以做的更加的优雅.想必少了那么多的各种流的Python Socket,你一定会喜欢的. 至于知识点相关的内容,这里就不多说了. UDP方式 ...
- 12 PopupWindow
PopupWindow创建方式 PopupWindow pop = new PopupWindow() PopupWindow pop = new PopupWindow(上下文, 填充宽, 填充高) ...
- Cocos2D iOS之旅:如何写一个敲地鼠游戏(三):素材最终解决方法
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 免责申明:本博客提供的所有翻译文章原稿均来自互联网,仅供学习交流 ...
- UNIX网络编程——经常使用的套接字选项
1.设置/获取套接字选项 int setsockopt(int socket, int level, int option_name, const void *option_value, sockle ...
- Android官方命令深入分析之etc1tool
etc1tool是一个命令行工具,可以将PNG图像压缩为etc1标准,并且可以进行解压缩. 用法: etc1tool infile [--help | --encode | --encodeNoHea ...
- SharePoint 2013: The "New Web Application" button is disabled is the central administration
安装完sharepoint foundation2013后,直接通过url访问管理中心,想进application management去新建的话会发现新建按钮是灰色的,即使你用的是系统管理员账号并且 ...
- iOS高效编程秘诀—坚持编程习惯
资料源于网络 习惯会影响一个人做事的方式,也会直接影响效率.我经常在项目完成后自我总结,有哪些做得好的,有哪些做得不好的?然后把一些好的流程记录下来,并且重新运用回编程中.那些能够坚持去做的流程,就变 ...