读写锁 ReadWriteLock
-
概念
维护一对关联锁,一个只用于读操作,一个只用于写操作;
读锁可以由多个读线程同时持有,写锁是排他的。同一时间,两把锁不能被不同线程持有。
目的是为了将读写分开,因为如果不分开的话,那么多个读锁想要同时获取的时候,还是需要等待,但是此时锁住的内容是没有改变的,这样就缇欧生了提升系统运行的效率。
例子:
package lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLock_Demo {
//不希望读写的内容都不一样,需要加入锁机制
volatile long i = 0;
Lock lock = new ReentrantLock();
ReadWriteLock rwLock = new ReentrantReadWriteLock();
// 使用读写锁的方式
public void read() {
rwLock.readLock().lock();
long iValue = i;
rwLock.readLock().unlock();
}
public void write() {
rwLock.writeLock().lock();
i++;
rwLock.writeLock().unlock();
}
// 非读写锁的方式
// public void read() {
// lock.lock();
//
// long a = i;
//
// lock.unlock();
// }
//
// public void write() {
// lock.lock();
//
// i++;
//
// lock.unlock();
// }
}
-
适用场景
适合读取操作多于写入操作的场景,改进互斥锁的性能,
比如:集合的并发线程安全性改造、缓存组件。
HashMap使用读写锁实现缓存,【读多写少】的例子
package lock;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @Author: Neco
* @Description: HashMap使用读写锁实现缓存,读多写少的例子
* @Date: create in 2022/6/20 17:59
*/
public class HashMap_Demo {
// 存放值的map
private final Map<String, Object> map = new HashMap<>();
private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读写锁
private final Lock readLock = readWriteLock.readLock(); // 读锁
private final Lock writeLock = readWriteLock.writeLock(); // 写锁
// 获取数据
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
// 放入缓存
public void put(String key, Object object) {
writeLock.lock();
try {
map.put(key, object);
} finally {
writeLock.unlock();
}
}
// 获取所有的key
public Object[] keys() {
readLock.lock();
try {
return map.keySet().toArray();
} finally {
readLock.unlock();
}
}
// 清空缓存
public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
// 其他的一些方法略……
}
-
锁降级
指的是写锁降级成为读锁。持有写锁的同时,再获取读锁,随后释放写锁的过程。写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的)
读写锁的实现代码 - 理论上实现的一个例子
- 锁的代码
package lock.readwritelock1;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/6/20 17:55
*/
public class NecoReadWriteLock {
// 一个read的count
AtomicInteger readCount = new AtomicInteger(0);
// 一个write的count
AtomicInteger writeCount = new AtomicInteger(0);
// 独占锁 拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
// 等待队列
public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<WaitNode>();
// 获取独占锁
public void lock() {
int arg = 1;
// 如果尝试,如果成功则方法正常退出,否则进行其他的处理
if (!tryLock(arg)) {
// 创建锁节点,并且标记为独占锁
WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
// 置入等待队列
waiters.offer(waitNode);
// 循环尝试获取锁
while (true) {
// 获取头部节点线程
WaitNode head = waiters.peek();
// 如果头节点不为空且为当前线程
if (head != null && head.thread == Thread.currentThread()) {
// 再次尝试获取 独占锁
if (!tryLock(arg)) {
// 若获取失败,挂起线程
LockSupport.park();
//若成功获取
} else {
// 将当前线程从队列头部移除,并退出方法
waiters.poll();
return;
}
}
}
}
}
// 释放独占锁
public void unlock() {
int arg = 1;
//尝试释放独占锁 若失败返回true,若失败...
if (tryUnlock(arg)) {
// 取出队列头部的元素
WaitNode head = waiters.peek();
if (head != null) {
Thread th = head.thread;
// 唤醒队列头部的线程
LockSupport.unpark(th);
}
}
}
//尝试获取独占锁
public boolean tryLock(int acquires) {
// 如果 被读锁占用 readCount > 0,则返回false
if (readCount.get() != 0) return false;
// 获取 独占锁 状态 0 未被占用
int wct = writeCount.get(); //
// wct == 0,表示未被占用
if (wct == 0) {
// 设置值,抢锁
if (writeCount.compareAndSet(wct, wct + acquires)) {
// 抢锁成功,设置owner
owner.set(Thread.currentThread());
return true;
}
// 如果已经被占用,且占用的线程为当前线程,则进行重入操作
} else if (owner.get() == Thread.currentThread()) {
writeCount.compareAndSet(wct, wct + acquires);
return true;
}
// 否则抢锁失败
return false;
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
// 如果当前线程没有持有独占锁,则抛出异常
if (owner.get() != Thread.currentThread()) throw new IllegalMonitorStateException();
// 获取 独占锁 状态 0 未被占用
int wct = writeCount.get();
// 计算 独占锁剩余占用,即释放了锁之后新的 writeCount 应该是多少
int nwct = wct - releases;
// 不管是否完全释放,都更新count值
writeCount.set(nwct);
// 如果完全释放,则将owner设置为null
if (nwct == 0) {
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
/************************* 共享锁 *****************************/
// 获取共享锁
public void lockShared() {
int arg = 1;
if (!tryLockShared(arg)) { //如果tryAcquireShare失败
// 创建锁节点,并且标记为共享锁
WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
// 将当前进程放入队列
waiters.offer(node); //加入队列
while (true) {
//若队列头部的元素是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
//尝试获取共享锁,若成功
if (tryLockShared(arg)) {
//将当前线程从队列中移除
waiters.poll();
WaitNode next = waiters.peek();
if (next != null && next.type == 1) { // 如果下一个线程也是等待共享锁
LockSupport.unpark(next.thread); // 将其唤醒
}
return; // 退出方法
} else { // 若尝试失败
LockSupport.park(); // 挂起线程
}
} else { // 若不是头部元素
LockSupport.park();
}
}
}
}
// 释放共享锁
public boolean unLockShared() {
int arg = 1;
if (tryUnLockShared(arg)) { //当read count变为0,才叫release share成功
WaitNode next = waiters.peek();
if (next != null) {
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
// 尝试获取共享锁
public boolean tryLockShared(int acquires) {
while (true) {
// 如果已经被独占锁占用,且当前独占锁所属的线程不是当前线程,加锁失败
if (writeCount.get() != 0 && owner.get() != Thread.currentThread()) {
return false;
}
// 获取共享锁状态
int rct = readCount.get();
// 加锁,修改状态,并且返回值
if (readCount.compareAndSet(rct, rct + acquires)) {
return true;
}
}
}
//尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
while (true) {
int rct = readCount.get();
int nrct = rct - releases;
if (readCount.compareAndSet(rct, nrct)) {
return nrct == 0;
}
}
}
class WaitNode {
int type = 0; //0 为想获取独占锁的线程, 1为想获取共享锁的线程
Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int arg) {
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
}
- 测试代码
package lock.readwritelock1;
/**
* @Author: Neco
* @Description:
* @Date: create in 2022/6/20 22:02
*/
public class ReadWriteLockDemo {
static NecoReadWriteLock rwLock = new NecoReadWriteLock();
static volatile int i = 0;
static void add() {
i++;
}
public static void main(String args[]) throws InterruptedException {
long startTime = System.currentTimeMillis();
for (int a = 1; a <= 20000; a++) {
final int n = a;
new Thread(new Runnable() {
@Override
public void run() {
if (n % 5 == 0) {
rwLock.lock();
add();
rwLock.unlock();
} else {
rwLock.lockShared();
System.out.println("i=" + i);
int a = i;
rwLock.unLockShared();
}
}
}).start();
}
while (true) {
System.out.println("目前耗时:" + (System.currentTimeMillis() - startTime) / 1000 + "s");
Thread.sleep(1000L);
System.out.println("i=" + i);
}
}
}
- 运行结果,截选
i=3996
i=3996
i=3996
i=3997
i=3997
i=3997
i=3997
i=3998
i=3998
i=3998
i=3998
i=3999
i=3999
目前耗时:1s
i=3999
i=3999
i=4000
目前耗时:2s
i=4000
目前耗时:3s
当然以上的代码还有很多可以修改的内容,比如抽取公共的方法,又比如实现公平锁和非公平锁的相关内容,具体的实现可以根据需求来继续扩充,以上的代码只是作为一个参考。
- 抽取重复代码块
package lock.readwritelock2;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.LockSupport;
public class CommonMask { //重复的代码提取到这里
volatile AtomicInteger readCount = new AtomicInteger(0);
AtomicInteger writeCount = new AtomicInteger(0);
//独占锁 拥有者
AtomicReference<Thread> owner = new AtomicReference<>();
//等待队列
public volatile LinkedBlockingQueue<WaitNode> waiters = new LinkedBlockingQueue<WaitNode>();
class WaitNode {
int type = 0; //0 为想获取独占锁的线程, 1为想获取共享锁的线程
Thread thread = null;
int arg = 0;
public WaitNode(Thread thread, int type, int arg) {
this.thread = thread;
this.type = type;
this.arg = arg;
}
}
//获取独占锁
public void lock() {
int arg = 1;
//尝试获取独占锁,若成功,退出方法, 若失败...
if (!tryLock(arg)) {
//标记为独占锁
WaitNode waitNode = new WaitNode(Thread.currentThread(), 0, arg);
waiters.offer(waitNode); //进入等待队列
//循环尝试拿锁
for (; ; ) {
//若队列头部是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (!tryLock(arg)) { //再次尝试获取 独占锁
LockSupport.park(); //若失败,挂起线程
} else { //若成功获取
waiters.poll(); // 将当前线程从队列头部移除
return; //并退出方法
}
} else { //若不是队列头部元素
LockSupport.park(); //将当前线程挂起
}
}
}
}
//释放独占锁
public boolean unlock() {
int arg = 1;
//尝试释放独占锁 若失败返回true,若失败...
if (tryUnlock(arg)) {
WaitNode next = waiters.peek(); //取出队列头部的元素
if (next != null) {
Thread th = next.thread;
LockSupport.unpark(th); //唤醒队列头部的线程
}
return true; //返回true
}
return false;
}
//尝试获取独占锁
public boolean tryLock(int acquires) {
//如果read count !=0 返回false
if (readCount.get() != 0)
return false;
int wct = writeCount.get(); //拿到 独占锁 当前状态
if (wct == 0) {
if (writeCount.compareAndSet(wct, wct + acquires)) { //通过修改state来抢锁
owner.set(Thread.currentThread()); // 抢到锁后,直接修改owner为当前线程
return true;
}
} else if (owner.get() == Thread.currentThread()) {
writeCount.set(wct + acquires); //修改count值
return true;
}
return false;
}
//尝试释放独占锁
public boolean tryUnlock(int releases) {
//若当前线程没有 持有独占锁
if (owner.get() != Thread.currentThread()) {
throw new IllegalMonitorStateException(); //抛IllegalMonitorStateException
}
int wc = writeCount.get();
int nextc = wc - releases; //计算 独占锁剩余占用
writeCount.set(nextc); //不管是否完全释放,都更新count值
if (nextc == 0) { //是否完全释放
owner.compareAndSet(Thread.currentThread(), null);
return true;
} else {
return false;
}
}
//获取共享锁
public void lockShared() {
int arg = 1;
if (tryLockShared(arg) < 0) { //如果tryAcquireShare失败
//将当前进程放入队列
WaitNode node = new WaitNode(Thread.currentThread(), 1, arg);
waiters.offer(node); //加入队列
for (; ; ) {
//若队列头部的元素是当前线程
WaitNode head = waiters.peek();
if (head != null && head.thread == Thread.currentThread()) {
if (tryLockShared(arg) >= 0) { //尝试获取共享锁, 若成功
waiters.poll(); //将当前线程从队列中移除
WaitNode next = waiters.peek();
if (next != null && next.type == 1) { //如果下一个线程也是等待共享锁
LockSupport.unpark(next.thread); //将其唤醒
}
return; //退出方法
} else { //若尝试失败
LockSupport.park(); //挂起线程
}
} else { //若不是头部元素
LockSupport.park();
}
}
}
}
//解锁共享锁
public boolean unLockShared() {
int arg = 1;
if (tryUnLockShared(arg)) { //当read count变为0,才叫release share成功
WaitNode next = waiters.peek();
if (next != null) {
LockSupport.unpark(next.thread);
}
return true;
}
return false;
}
//尝试获取共享锁
public int tryLockShared(int acquires) {
for (; ; ) {
if (writeCount.get() != 0 &&
owner.get() != Thread.currentThread())
return -1;
int rct = readCount.get();
if (readCount.compareAndSet(rct, rct + acquires)) {
return 1;
}
}
}
//尝试解锁共享锁
public boolean tryUnLockShared(int releases) {
for (; ; ) {
int rc = readCount.get();
int nextc = rc - releases;
if (readCount.compareAndSet(rc, nextc)) {
return nextc == 0;
}
}
}
}
AQS (AbstractQueuedSynchronizer)
实际上,AQS实现对我们同步状态的管理,以及阻塞线程进行排队等待通知的一系列底层实现的处理,AQS核心其实就包含了以下几个内容:
- 同步队列
- 锁的释放和获取,包含了独占锁和共享锁一系列的实现
- 本质来说,跟上方的重复代码块的内容(模板方法)类似
- AQS中是使用链表来实现等待队列的
AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
AQS 抽象队列同步器
- 提供了对资源占用、释放,线程的挂起、唤醒的逻辑。
- 预留了各种 try方法给用户实现
- 可以用在各种需要控制资源争用的场景中。(ReentrantLock/CountDownLatch/Semphore)
此外 ReadWriteLock 使用了一个int 存储了两个count的值,以解决readCount和writeCount的在读写线程同时操作时可能冲突的问题
如果觉得有收获就点个赞吧,更多知识,请点击关注查看我的主页信息哦~