今天给大家介绍下读写锁,在引入读写锁之前,给大家说一个案例:
在很多场景下,我们用到的都是互斥锁,线程间相互竞争资源;但是有时候我们的场景会存在读多写少的情况,这个时候如果还是使用互斥锁,就会导致资源的浪费,为什么呢?
因为如果同时都在读的时候,是不需要锁资源的,只有读和写在同时工作的时候才需要锁资源,所以如果直接用互斥锁,肯定会导致资源的浪费。
获取读锁对象(不是获取锁资源)
获取写锁对象(不是获取锁资源)
获取当前读锁被获取的次数,包括线程重入的次数
返回当前线程获取读锁的次数
判断写锁是否被获取
返回当前写锁被获取的次数
package com.ams.thread.lesson8;
import cn.hutool.core.thread.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 关注微信公众号"AI码师"获取项目源码及2021面试题一套
*
* @author: AI码师
* Date: 2021/12/30 6:08 上午
* Description:
*/
@Slf4j
public class Example20 {
private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
private static Lock readLock = lock.readLock();
private static Lock writeLock = lock.writeLock();
public static void main(String[] args) {
new Thread(new Thread1("读线程1")).start();
new Thread(new Thread1("读线程2")).start();
new Thread(new Thread1("读线程3")).start();
new Thread(new Thread1("读线程4")).start();
new Thread(new Thread2("写线程1")).start();
new Thread(new Thread2("写线程2")).start();
}
/**
* 读锁线程
*/
static class Thread1 implements Runnable {
private String name;
public Thread1(String name) {
this.name = name;
}
@Override
public void run() {
readLock.lock();
int i=0;
while (i++<10){
log.info("线程 {} 在运行",name);
ThreadUtil.sleep(1000);
}
readLock.unlock();
}
}
/**
* 写锁线程
*/
static class Thread2 implements Runnable {
private String name;
public Thread2(String name) {
this.name = name;
}
@Override
public void run() {
writeLock.lock();
int i=0;
while (i++<5){
log.info("线程 {} 在运行",name);
ThreadUtil.sleep(1000);
}
writeLock.unlock();
}
}
}
从结果中可以看出来有三种结果,所以分析看来
前面我们说过,并发包中的锁都是基于AQS实现的,锁的状态都是基于state变量进行维护的,那么在读写锁中,state是如何维护这两个状态呢?
我们想必在读其它框架源码时候,应该也会看到过类似的问题,既然想要一个变量表示多个状态,那么我们就得把这个变量进行拆分,就是拆成bit位进行表示,没错:读写锁的状态位就是这么设计的!
我们分析下读写状态位的设计:
获取锁的源码
protected final boolean tryAcquire(int acquires) {
Thread current = Thread.currentThread();
int c = getState();
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
if (w == 0 || current != getExclusiveOwnerThread())
return false;
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
setState(c + acquires);
return true;
}
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
setExclusiveOwnerThread(current);
return true;
}
释放锁的源码
protected final boolean tryRelease(int releases) {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
int nextc = getState() - releases;
boolean free = exclusiveCount(nextc) == 0;
if (free)
setExclusiveOwnerThread(null);
setState(nextc);
return free;
}
获取锁的源码
protected final int tryAcquireShared(int unused) {
Thread current = Thread.currentThread();
int c = getState();
if (exclusiveCount(c) != 0 &&
getExclusiveOwnerThread() != current)
return -1;
int r = sharedCount(c);
if (!readerShouldBlock() &&
r < MAX_COUNT &&
compareAndSetState(c, c + SHARED_UNIT)) {
if (r == 0) {
firstReader = current;
firstReaderHoldCount = 1;
} else if (firstReader == current) {
firstReaderHoldCount++;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
cachedHoldCounter = rh = readHolds.get();
else if (rh.count == 0)
readHolds.set(rh);
rh.count++;
}
return 1;
}
return fullTryAcquireShared(current);
}
释放锁的源码
protected final boolean tryReleaseShared(int unused) {
Thread current = Thread.currentThread();
if (firstReader == current) {
// assert firstReaderHoldCount > 0;
if (firstReaderHoldCount == 1)
firstReader = null;
else
firstReaderHoldCount--;
} else {
HoldCounter rh = cachedHoldCounter;
if (rh == null || rh.tid != getThreadId(current))
rh = readHolds.get();
int count = rh.count;
if (count <= 1) {
readHolds.remove();
if (count <= 0)
throw unmatchedUnlockException();
}
--rh.count;
}
for (;;) {
int c = getState();
int nextc = c - SHARED_UNIT;
if (compareAndSetState(c, nextc))
// Releasing the read lock has no effect on readers,
// but it may allow waiting writers to proceed if
// both read and write locks are now free.
return nextc == 0;
}
}
当前线程先获取到写锁,然后再获取读锁,再把写锁释放,最后释放读锁
关注公众号领取2022最新面试题一套和项目源码