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 ...
随机推荐
- 改进版getpass库
编程伊始 正式实施 改进版 源码 以数字显示 以自定义分隔符delimiter显示 如何使用 下载及安装 在您的代码中使用 源码下载 总结 用过Linux的都知道,尤其是进行使用包管理软件类似于apt ...
- Android Lollipop 5.0 经典新特性回顾
*Tamic 专注移动开发! 更多文章请关注 http://blog.csdn.net/sk719887916 虽然Android已到了7.0 ,但是我们还是不能忘怀视觉革命性改变的5.0,今天回顾下 ...
- App安全(一) Android防止升级过程被劫持和换包
文/ Tamic 地址/ http://blog.csdn.net/sk719887916/article/details/52233112 前言 APP 安全一直是开发者头痛的事情,越来越多的安全漏 ...
- Android Studio中Git的配置及协同开发
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51595096 本文出自:[openXu的博客] 目录: 一 Android Stutio配置 ...
- Java多线程模型
谈到Java多线程就涉及到多线程的模型及Java线程与底层操作系统之间的关系.正如我们熟知,现代机器可以分为硬件和软件两大块,如图2-5-1-1,硬件是基础,软件提供实现不同功能的手段.而且软件可以分 ...
- Nginx的负载均衡 - 一致性哈希 (Consistent Hash)
Nginx版本:1.9.1 我的博客:http://blog.csdn.net/zhangskd 算法介绍 当后端是缓存服务器时,经常使用一致性哈希算法来进行负载均衡. 使用一致性哈希的好处在于,增减 ...
- [EXTJS5学习笔记]第二十六节 在eclipse/myeclipse中使用sencha extjs的插件
本文地址:http://blog.csdn.net/sushengmiyan/article/details/40507383 插件下载: http://download.csdn.net/detai ...
- 为什么不要在viewDidLoad方法中设置开始监听键盘通知
大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请多提意见,如果觉得不错请多多支持点赞.谢谢! hopy ;) 一个普遍的错误是,程序猿(媛)试图在view controll ...
- Android简易实战教程--第十二话《代码获取手机总运行内存的大小》
手机RAM存储,类似于电脑的内存.这一篇,对通过代码获取手机总内存大小做详细介绍. 首先,定义一个engine类,这个类功能就是获取进程信息,包括运行的程序个数,系统总内存,系统剩余总内存.本篇先完成 ...
- ubuntu连接android设备(附最简单方法)
在ubuntu下连接android设备,虽然不用像windows那样安装驱动,然而却会遇见一个错误:输入adb shell,会提示insufficient permissions for device ...