并发库应用之五 & ReadWriteLock场景应用
锁降级:从写锁变成读锁;
锁升级:从读锁变成写锁。
读锁是可以被多线程共享的,写锁是单线程独占的。也就是说写锁的并发限制比读锁高,这可能就是升级/降级名称的来源。
如下代码会产生死锁,因为同一个线程中,在没有释放读锁的情况下,就去申请写锁,这属于锁升级,ReentrantReadWriteLock是不支持的。
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("get readLock.");
rtLock.writeLock().lock();
System.out.println("blocking");
ReentrantReadWriteLock支持锁降级,如下代码不会产生死锁。
ReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock"); rtLock.readLock().lock();
System.out.println("get read lock");
以上这段代码虽然不会导致死锁,但没有正确的释放锁。从写锁降级成读锁,并不会自动释放当前线程获取的写锁,仍然需要显示的释放,否则别的线程永远也获取不到写锁。
class CachedData {
Object data;
volatile boolean cacheValid;
final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public 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;
}
// 在释放写锁之前通过获取读锁降级写锁(注意此时还没有释放写锁)
rwl.readLock().lock();
} finally {
rwl.writeLock().unlock(); // 释放写锁而此时已经持有读锁
}
}
try {
use(data);
} finally {
rwl.readLock().unlock();
}
}
}
以上代码加锁的顺序为:
1. rwl.readLock().lock();
2. rwl.readLock().unlock();
3. rwl.writeLock().lock();
4. rwl.readLock().lock();
5. rwl.writeLock().unlock();
6. rwl.readLock().unlock();
以上过程整体讲解:
1. 多个线程同时访问该缓存对象时,都加上当前对象的读锁,之后其中某个线程优先查看data数据是否为空。【加锁顺序序号:1 】
2. 当前查看的线程发现没有值则释放读锁立即加上写锁,准备写入缓存数据。(不明白为什么释放读锁的话可以查看上面讲解进入写锁的前提条件)【加锁顺序序号:2和3 】
3. 为什么还会再次判断是否为空值(!cacheValid)是因为第二个、第三个线程获得读的权利时也是需要判断是否为空,否则会重复写入数据。
4. 写入数据后先进行读锁的降级后再释放写锁。【加锁顺序序号:4和5 】
5. 最后数据数据返回前释放最终的读锁。【加锁顺序序号:6 】
如果不使用锁降级功能,如先释放写锁,然后获得读锁,在这个get过程中,可能会有其他线程竞争到写锁 或者是更新数据 则获得的数据是其他线程更新的数据,可能会造成数据的污染,即产生脏读的问题。
下面,让我们来实现真正趋于实际生产环境中的缓存案例:
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock; public class CacheDemo {
/**
* 缓存器,这里假设需要存储1000左右个缓存对象,按照默认的负载因子0.75,则容量=750,大概估计每一个节点链表长度为5个
* 那么数组长度大概为:150,又有雨设置map大小一般为2的指数,则最近的数字为:128
*/
private Map<String, Object> map = new HashMap<>(128);
private ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) { }
public Object get(String id){
Object value = null;
rwl.readLock().lock();//首先开启读锁,从缓存中去取
try{
if(map.get(id) == null){ //如果缓存中没有释放读锁,上写锁
rwl.readLock().unlock();
rwl.writeLock().lock();
try{
if(value == null){ //防止多写线程重复查询赋值
value = "redis-value"; //此时可以去数据库中查找,这里简单的模拟一下
}
rwl.readLock().lock(); //加读锁降级写锁,不明白的可以查看上面锁降级的原理与保持读取数据原子性的讲解
}finally{
rwl.writeLock().unlock(); //释放写锁
}
}
}finally{
rwl.readLock().unlock(); //最后释放读锁
}
return value;
}
}
提示:读写锁之后有一个与它配合使用的有条件的阻塞,可以实现线程间的通信,它就是Condition。具体详情请查看我的博客:并发库应用之六 & 有条件阻塞Condition应用
并发库应用之五 & ReadWriteLock场景应用的更多相关文章
- 并发库应用之四 & 线程锁Lock应用
Java5的线程并发库中,提供了相应的线程锁接口Lock来帮助我们同步处理.Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就 ...
- 线程高级应用-心得5-java5线程并发库中Lock和Condition实现线程同步通讯
1.Lock相关知识介绍 好比我同时种了几块地的麦子,然后就等待收割.收割时,则是哪块先熟了,先收割哪块. 下面举一个面试题的例子来引出Lock缓存读写锁的案例,一个load()和get()方法返回值 ...
- Java线程新特征——Java并发库
一.线程池 Sun在Java5中,对Java线程的类库做了大量的扩展,其中线程池就是Java5的新特征之一,除了线程池之外,还有很多多线程相关的内容,为多线程的编程带来了极大便利.为了编写高效稳定 ...
- java--加强之 Java5的线程并发库
转载请申明出处:http://blog.csdn.net/xmxkf/article/details/9945499 01. 传统线程技术回顾 创建线程的两种传统方式: 1.在Thread子类覆盖的r ...
- 并发库应用之七 & 信号灯Semaphore应用
Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制. Semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中 的任何一个让开后,其中 ...
- 并发库应用之一 & ThreadLocal实现线程范围的共享变量
ThreadLocal用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据. 每个线程调用全局ThreadLocal对象的 ...
- 03.Java多线程并发库API使用2
1.多个线程之间共享数据的方式探讨 1.如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做. 2.如果每个线程执行的代 ...
- Java多线程与并发库高级应用-java5线程并发库
java5 中的线程并发库 主要在java.util.concurrent包中 还有 java.util.concurrent.atomic子包和java.util.concurrent.lock子包 ...
- java并发库_并发库知识点整理
并发库(java.util.concurrent)中的工具数不胜数,那么我们梳理一下线程并发库中重要的一些常用工具: 1.
随机推荐
- Android群英传笔记——第十二章:Android5.X 新特性详解,Material Design UI的新体验
Android群英传笔记--第十二章:Android5.X 新特性详解,Material Design UI的新体验 第十一章为什么不写,因为我很早之前就已经写过了,有需要的可以去看 Android高 ...
- ubuntu14.04系统中virtualbox安装Oracle VM VirtualBox Extension Pack包
ubuntu14.04系统中virtualbox默认不支持usb设备,需要安装Oracle VM VirtualBox Extension Pack才行,但必须安装以下版本才可以安装成功: Oracl ...
- 计算机编码方式详解(Unicode、UTF-8、UTF-16、ASCII)
整理这篇文章的动机是两个问题: 问题一: 使用Windows记事本的"另存为",可以在GBK.Unicode.Unicode big endian和UTF-8这几种编码方式间相互转 ...
- SpriteBuilder修改CCB文件中的子CCB文件需要注意的一个地方
在SpriteBuilder中如果一个CCB(比如一个场景)中嵌入了另一个子CCB文件(比如一个player),那么当给该子CCB中的root对象添加若干属性的时候,必须注意到这个并没有应用到父CCB ...
- 菜鸟玩云计算之十七:RHEL克隆虚拟机后改变网卡地址
菜鸟玩云计算之十七:RHEL克隆虚拟机后改变网卡地址 (一)更改hostname # vi /etc/sysconfig/network NETWORKING=yes HOSTNAME=your-vm ...
- unity 球体表面平均分割点
之前看了别人的一份源码,讲到了球体表面平均分割点,于是也好奇去查了一下算法,自己写不出来,借用算法在unity写了一个小demo using UnityEngine; using System.Col ...
- linux - 目录、文件默认属性: umask使用
一 权限掩码umask umask是chmod配套的,总共为4位(gid/uid,属主,组权,其它用户的权限),不过通常用到的是后3个,例如你用chmod 755 file(此时这文件的权限是属主读( ...
- ANDROID 中设计模式的采用--结构型模式
结构型模式中的适配器模式.外观模式.装饰模式.代理模式都属于包装模式,都是对另外的类或对象的包装,只是各自的意图不同. 适配器模式通过对另外的类或对象的包装,将其接口转换为用户期望 ...
- linux的link命令
sudo ln -s 源文件 目标文件 sudo ln -s /usr/local/mysql/bin/mysqladmin /sbin/mysqladmin 建立软连接 ln -d existfil ...
- 利用PreLoader实现一个平视显示(HUD)效果(可以运用到加载等待效果),并进行简单的讲解
什么是PreLoader? PreLoader是由Volodymyr Kurbatov设计的一个很有意思的HUD(平视显示效果(Head Up Display)),通过运动污点和固定污点之间的粘黏动画 ...