从ReentrantReadWriteLock开始的独占锁与共享锁的源码分析
创始人
2024-01-31 17:30:14
0

FBI WARNING(bushi)

当涉及sync调用时,并不会分析尝试获取和释放之后的后继逻辑,因为这个逻辑是由AQS类实现的。请看姊妹篇之并发入门组件AQS源码解析。

开始的开始是一个demo

以下的代码,会将独占锁持有5分钟,在此期会阻塞住后面两个线程的共享锁请求,独占锁释放后,两个线程会同时拥有共享锁,即使没有进行unlock()

public class lockTest {public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();public static void main(String[] args) {new Thread(()->{writeLock.lock();try {Thread.sleep(300000);System.out.println("写锁释放");} catch (InterruptedException e) {e.printStackTrace();}writeLock.unlock();}).start();new Thread(()->{readLock.lock();try {System.out.println("读锁1");Thread.sleep(300000);} catch (Exception e) {e.printStackTrace();}readLock.unlock();}).start();new Thread(()->{readLock.lock();try {System.out.println("读锁1");Thread.sleep(300000);} catch (Exception e) {e.printStackTrace();}readLock.unlock();}).start();}
}

ReentranReadWriteLock,writeLock,readLock,AQS的关系

让我们先进入第一行,
public static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
可以看到,默认创建了一个非公平的AQS,然后将这个AQS分别给了读锁和写锁。
请添加图片描述
请添加图片描述
而后面两行

static ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
static ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
就只是返回已经建立好的对象
请添加图片描述

独占锁获取

进入

我们进入上面的代码writeLock.lock();
在这里插入图片描述
进入了老熟客acquire方法。
在这里插入图片描述

核心方法

总体大概思路是,判断是否锁是否有被使用,如果有被使用而是自己,那重入,不然就失败,如果没被使用就设定为自己。

protected final boolean tryAcquire(int acquires) {Thread current = Thread.currentThread();//获取状态并且检查独占进入次数int c = getState();int w = exclusiveCount(c);if (c != 0) {// 独占次数为0,但是状态却不为0,那么说明现在存在着共享锁。如果独占锁不为自己,状态却不为0,那么说明现在存在着独占锁,这两种情况都无法获取到锁if (w == 0 || current != getExclusiveOwnerThread())return false;// 达到MAX_COUNT说明重入了65536次,九成九九九都是代码写错了!if (w + exclusiveCount(acquires) > MAX_COUNT)throw new Error("Maximum lock count exceeded");// 状态加上当前重入次数setState(c + acquires);return true;}//前一个条件是公平锁和非公平锁的关键差异。后一个,如果c发生了变化,说明有线程同样参与了该AQS状态的变化并且早于自己完成了修改,那么默认自己失败,放弃。if (writerShouldBlock() ||!compareAndSetState(c, c + acquires))return false;//将独占锁所有者设定为自己,并返回成功获取setExclusiveOwnerThread(current);return true;}

请添加图片描述

state

state是AQS里面一个32位长的整型。
private volatile int state;
后16位说明独占锁状态,数值表示这个独占锁被持有者重入了多少次。
前16位说明共享锁状态,数值表示这个共享锁被持有了多少次。(请注意,一个线程可以多次持有读锁)

公平与非公平的界定

我们可以清晰看到,公平和非公平只在于writerShouldBlock()和readerShouldBlock()的重写
请添加图片描述
先看看非公平锁,非公平锁并不会对独占进行操作
请添加图片描述
再看看公平锁,写锁会进行判断。如果队列不为空,并且队列里争抢头节点的线程不为自己,说明有线程在排队,于是tryAcquired失败,
请添加图片描述

请添加图片描述

独占锁释放

请添加图片描述
实际上release返回false并不重要,我们可以看出上层并没有对其返回值进行逻辑操作。顺带一提,unparkSuccessor也在AQS篇。
请添加图片描述

protected final boolean tryRelease(int releases) {// 进行锁持有检验与释放if (!isHeldExclusively())throw new IllegalMonitorStateException();int nextc = getState() - releases;// 如果释放了之后不为0,说明重入了,那么在release层就直接返回false而不执行后续的唤醒后继者。boolean free = exclusiveCount(nextc) == 0;if (free)setExclusiveOwnerThread(null);setState(nextc);return free;
}

共享锁获取

进入

回到最初的最初,让我们看一下共享锁的获取流程
请添加图片描述
调用的是ReadLock下面的lock(),可以看出,也是对sync进行调用操作,只不过独占锁调用的是acquire,而共享锁调用的是acquireShared
请添加图片描述
try尝试获取一下
请添加图片描述

核心方法

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);//如果成功获取,那么必定返回1if (!readerShouldBlock() &&r < MAX_COUNT &&compareAndSetState(c, c + SHARED_UNIT)) {if (r == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {//如果缓存不是自己的HoldCounter,就去ThreadLocalMap取出线程私有的HoldCounter,存入缓存HoldCounter rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))cachedHoldCounter = rh = readHolds.get();// 如果自己缓存数量为0,就放入count为1的HoldCounterelse if (rh.count == 0)readHolds.set(rh);rh.count++;}return 1;}//如果不是因为存在独占锁,而是因为共享锁共同compareAndSetState导致失败,或者队列中首线程正在争抢独占锁(还没抢到),那么就会进入完全尝试return fullTryAcquireShared(current);
}

请添加图片描述
请添加图片描述
如果头节点不为空,并且头节点的下一个不为空,并且头节点的下一个不是共享的,并且该节点没有被取消,那么就判定正在争抢锁的线程是抢独占锁的。如果对方抢独占锁,那就直接放行,这是为了避免前面判断完了非独占,但是到了这一行,其他线程又已经上了独占锁这种尴尬现象。
请添加图片描述

进入完全尝试

完全尝试分为三部分,一,有独占锁且不是自己,直接失败。二,自己没有获取过读锁只是首次尝试,直接返回false,不再尝试。三,前两者都通过的情况下,尝试成功获取锁。

        final int fullTryAcquireShared(Thread current) {HoldCounter rh = null;for (;;) {int c = getState();//如果存在独占锁,但不是自己,直接失败,if (exclusiveCount(c) != 0) {if (getExclusiveOwnerThread() != current)return -1;//readerShouldBlock上面有解析,说明有线程在抢独占锁} else if (readerShouldBlock()) {if (firstReader == current) {// assert firstReaderHoldCount > 0;} else {// 如果自己不是第一个获取到读锁的,又没有成功获取到读锁过,那么把自己的计数器给删了if (rh == null) {rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current)) {rh = readHolds.get();if (rh.count == 0)readHolds.remove();}}if (rh.count == 0)return -1;}}if (sharedCount(c) == MAX_COUNT)throw new Error("Maximum lock count exceeded");if (compareAndSetState(c, c + SHARED_UNIT)) {if (sharedCount(c) == 0) {firstReader = current;firstReaderHoldCount = 1;} else if (firstReader == current) {firstReaderHoldCount++;} else {if (rh == null)rh = cachedHoldCounter;if (rh == null || rh.tid != getThreadId(current))rh = readHolds.get();else if (rh.count == 0)readHolds.set(rh);rh.count++;cachedHoldCounter = rh; // cache for release}return 1;}}}

释放共享锁

进入

执行readLock.unlock();
在这里插入图片描述
在这里插入图片描述

核心方法

protected final boolean tryReleaseShared(int unused) {Thread current = Thread.currentThread();// 如果是第一个获取共享锁的,直接将firstReader改为nullif (firstReader == current) {// assert firstReaderHoldCount > 0;if (firstReaderHoldCount == 1)firstReader = null;elsefirstReaderHoldCount--;} else {// 否则取出当前线程的重入次数,并减一,如果只剩一次了,那么就从map中删除当前线程的计数器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;}//一直尝试将状态值减1直到成功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;}}

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...