ReadWriteLock读写锁(八)
前言:在JUC ReentrantReadWriteLock是基于AQS实现的读写锁实现。
ReadWriteLock中定义了读写锁需要实现的接口,具体定义如下:
public interface ReadWriteLock {
//创建一个读锁
Lock readLock();
//创建一个写锁
Lock writeLock();
}
一、ReadWriteLock读写锁适用场景
在ReentrantLock中,线程之间的同步都是互斥的,不管是读操作还是写操作,但是在一些场景中读操作是可以并行进行的,只有写操作才是互斥的,这种情况虽然也可以使用ReentrantLock来解决,但是在性能上也会损失,ReadWriteLock就是用来解决这个问题的。
重入锁ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程可以进行访问,但是在大多数场景下,大部分时间都是提供读服务,而写服务占有的时间较少。然而读服务不存在数据竞争问题,如果一个线程在读时禁止其他线程读势必会导致性能降低。所以就提供了读写锁。
读写锁维护着一对锁,一个读锁和一个写锁。通过分离读锁和写锁,使得并发性比一般的排他锁有了较大的提升:在同一时间可以允许多个读线程同时访问,但是在写线程访问时,所有读线程和写线程都会被阻塞。
读写锁适用于读多写少的情况。
二、ReadWriteLock读写锁
读写锁的主要特性:
- 公平性:支持公平性和非公平性。
- 重入性:支持重入。读写锁最多支持65535个递归写入锁和65535个递归读取锁。
- 锁降级:遵循获取写锁、获取读锁在释放写锁的次序,写锁能够降级成为读锁
读写锁ReentrantReadWriteLock实现接口ReadWriteLock,该接口维护了一对相关的锁,一个用于只读操作,另一个用于写入操作。只要没有 writer,读取锁可以由多个 reader 线程同时保持。写入锁是独占的。
ReadWriteLock定义了两个方法。readLock()返回用于读操作的锁,writeLock()返回用于写操作的锁。
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
三、ReentrantReadWriteLock可重入读写锁
/** 内部类 读锁 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 内部类 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}
/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/** 返回用于写入操作的锁 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于读取操作的锁 */
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer{
/**
* 省略其余源代码
*/
}
public static class WriteLock implements Lock, java.io.Serializable{
/**
* 省略其余源代码
*/
}
public static class ReadLock implements Lock, java.io.Serializable {
/**
* 省略其余源代码
*/
}
ReentrantReadWriteLock与ReentrantLock一样,其锁主体依然是Sync,它的读锁、写锁都是依靠Sync来实现的。所以ReentrantReadWriteLock实际上只有一个锁,只是在获取读取锁和写入锁的方式上不一样而已,它的读写锁其实就是两个类:ReadLock、writeLock,这两个类都是lock实现。
在ReentrantLock中使用一个int类型的state来表示同步状态,该值表示锁被一个线程重复获取的次数。但是读写锁ReentrantReadWriteLock内部维护着两个一对锁,需要用一个变量维护多种状态。所以读写锁采用“按位切割使用”的方式来维护这个变量,将其切分为两部分,高16为表示读,低16为表示写。分割之后,读写锁是如何迅速确定读锁和写锁的状态呢?通过为运算。假如当前同步状态为S,那么写状态等于 S & 0x0000FFFF(将高16位全部抹去),读状态等于S >>> 16(无符号补0右移16位)。代码如下:
static final int SHARED_SHIFT = 16;
static final int SHARED_UNIT = (1 << SHARED_SHIFT);
static final int MAX_COUNT = (1 << SHARED_SHIFT) - 1;
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
static int sharedCount(int c) { return c >>> SHARED_SHIFT; }
static int exclusiveCount(int c) { return c & EXCLUSIVE_MASK; }
(1)写锁
写锁就是一个支持可重入的排他锁。
写锁的获取:
写锁的获取最终会调用tryAcquire(int arg),该方法在内部类Sync中实现:
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
//当前锁个数
int c = getState();
//写锁
int w = exclusiveCount(c);
if (c != 0) {
//c != 0 && w == 0 表示存在读锁
//当前线程不是已经获取写锁的线程
if (w == 0 || current != getExclusiveOwnerThread())
return false;
//超出最大范围
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
setState(c + acquires);
return true;
}
//是否需要阻塞
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
//设置获取锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
该方法和ReentrantLock的tryAcquire(int arg)大致一样,在判断重入时增加了一项条件:读锁是否存在。因为要确保写锁的操作对读锁是可见的,如果在存在读锁的情况下允许获取写锁,那么那些已经获取读锁的其他线程可能就无法感知当前写线程的操作。因此只有等读锁完全释放后,写锁才能够被当前线程所获取,一旦写锁获取了,所有其他读、写线程均会被阻塞。
写锁的释放:
获取了写锁用完了则需要释放,WriteLock提供了unlock()方法释放写锁:
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
写锁的释放最终还是会调用AQS的模板方法release(int arg)方法,该方法首先调用tryRelease(int arg)方法尝试释放锁,tryRelease(int arg)方法为读写锁内部类Sync中定义了,如下:
protected final boolean tryRelease(int releases) {
//释放的线程不为锁的持有者
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
//若写锁的新线程数为0,则将锁的持有者设置为null
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
写锁释放锁的整个过程和独占锁ReentrantLock相似,每次释放均是减少写状态,当写状态为0时表示 写锁已经完全释放了,从而等待的其他线程可以继续访问读写锁,获取同步状态,同时此次写线程的修改对后续的线程可见。
(2)读锁
读锁为一个可重入的共享锁,它能够被多个线程同时持有,在没有其他写线程访问时,读锁总是或获取成功。
获取读锁总结:
- 如果不存在线程持有写锁,则获取读锁成功。
- 如果其他线程持有写锁,则获取读锁失败。
- 如本线程持有写锁,并且不存在等待写锁的其他线程,则获取读锁成功。
- 如本线程持有写锁,并且存在等待写锁的其他线程,则如果本线程已经持有读锁,则获取读锁成功,如果不能存在读锁,则此次获取读锁失败。
四、锁降级
读写锁有一个特性就是锁降级,锁降级就意味着写锁是可以降级为读锁的,但是需要遵循先获取写锁、获取读锁在释放写锁的次序。注意如果当前线程先获取写锁,然后释放写锁,再获取读锁这个过程不能称之为锁降级,锁降级一定要遵循那个次序。
锁降级中读锁的获取释放为必要?肯定是必要的。试想,假如当前线程A不获取读锁而是直接释放了写锁,这个时候另外一个线程B获取了写锁,那么这个线程B对数据的修改是不会对当前线程A可见的。如果获取了读锁,则线程B在获取写锁过程中判断如果有读锁还没有释放则会被阻塞,只有当前线程A释放读锁后,线程B才会获取写锁成功。
参考链接
本文主要整理自互联网,主要是便于自己复习知识所用,侵权联系删除,以下为原文参考链接!
【2】【死磕Java并发】—–J.U.C之读写锁:ReentrantReadWriteLock
【3】JUC 可重入 读写锁 [ReentrantReadWriteLock]
ReadWriteLock读写锁(八)的更多相关文章
- 12. ReadWriteLock 读写锁
package com.gf.demo11; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent. ...
- java多线程 -- ReadWriteLock 读写锁
写一条线程,读多条线程能够提升效率. 写写/读写 需要“互斥”;读读 不需要互斥. ReadWriteLock 维护了一对相关的锁,一个用于只读操作,另一个用于写入操作.只要没有 writer,读取锁 ...
- GUC-9 ReadWriteLock : 读写锁
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWrit ...
- ReadWriteLock: 读写锁
ReadWriteLock: 读写锁 ReadWriteLock: JDK1.5提供的读写分离锁,采用读写锁分离可以有效帮助减少锁竞争. 特点: 1).使用读写锁.当线程只进行读操作时,可以允许多个线 ...
- 22.ReadWriteLock读写锁
import java.util.Random; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.R ...
- 【转】java并发编程系列之ReadWriteLock读写锁的使用
前面我们讲解了Lock的使用,下面我们来讲解一下ReadWriteLock锁的使用,顾明思义,读写锁在读的时候,上读锁,在写的时候,上写锁,这样就很巧妙的解决synchronized的一个性能问题:读 ...
- Java多线程之ReadWriteLock读写锁简介与使用教程
转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6558073.html 普通的锁在对某一内容加锁后,其他线程是不能访问的.但是我们要考虑这种情况:如果当前加锁 ...
- ReadWriteLock 读写锁(读书笔记)
读写分离锁可以有效的帮助减少锁的竞争,提升系统的效率, 读-读不互斥 读读之间不阻塞 读-写互斥 读阻塞写,写也会阻塞读 写-写互斥 写写阻塞 在系统中,读操作次数远远大于写操作,则读写锁就可以发挥 ...
- Java并发:ReadWriteLock 读写锁
读写锁在同一时刻可以允许多个线程访问,但是在写线程访问,所有的读线程和其他写线程均被阻塞. 读写锁不像 ReentrantLock 那些排它锁只允许在同一时刻只允许一个线程进行访问,读写锁可以允许多个 ...
随机推荐
- 【CTF杂项】常见文件文件头文件尾格式总结及各类文件头
文件头文件尾总结 JPEG (jpg), 文件头:FFD8FF 文件尾:FF D9PNG (png), 文件头:89504E47 文件尾:AE 42 60 82GIF (gif), 文件头:47494 ...
- 将 ASP.NET Core 2.0 项目升级至 ASP.NET Core 2.1.3X
在上一篇文章ASP.Net Core 运行错误 Http Error 502.5 解决办法的最后有提到说,最推荐的升级办法是从2.0升级到2.1X版本. 操作如下 项目的例子直接使用https://g ...
- flask使用基础
1.安装 pip install Flask 基本依赖库: jinja2:实现对模板的处理 werkzeug:本质是socket服务器,用于接收http请求,并对请求进行预处理,然后触发Flaks框架 ...
- Vue(二)基础
01-vue的起步 1.引包 a) 直接下载,并用<script>标签引入 b) CDN方式引入: <script src="https://cdn.bootcss.com ...
- Mysql乱码问题总结
这两天研究了下Mysql的字符集编码和排序规则,有个很典型的问题就是乱码问题.所以小记一下. http://www.jianshu.com/p/4c6a27542df4 http://blog.csd ...
- [Offer收割]编程练习赛97
链接 [https://hihocoder.com/contest/offers97/problems] 题意 题目1 : 放置矩形 时间限制:10000ms 单点时限:1000ms 内存限制:256 ...
- c++入门之——const在函数名前面和函数后面的区别
class Test(){ public: Test(){} const int foo(int a); const int foo(int a) const; }; 一.概念 当const在函数名前 ...
- PyQuery库
'''强大又灵活的网页解析库.如果你觉得正则写起来太麻烦,又觉得BeautifulSoup语法太难记,如果你熟悉jQuery的语法,那么PyQuery就是你的绝佳选择.'''from pyquery ...
- PS制作墙壁上海报卷页图片效果
1.首先,打开PS,新建合适的画布. 2.为了使背景具有质感,执行滤镜—滤镜库—纹理化,具体参数按你的感觉来. 3.新建画布“图层1”,为了方便观察,填充为灰色画布,ctrl+t适当缩小画布大小,如图 ...
- 简约时尚商城wordpress主题-storefront
wordpress主题:简约时尚商城主题-storefront 简简单的商城模板,挺适合一些懒人所用.后天功能也挺不错,希望大家喜欢. WooCommerce 集成 商城是基为用 WooCommerc ...