Java并发编程之AbstractQueuedSynchronizer队列同步器与可重入锁ReentrantLock
2022/6/20 5:20:23
本文主要是介绍Java并发编程之AbstractQueuedSynchronizer队列同步器与可重入锁ReentrantLock,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
前言:
之前有写过关于重入锁ReentrantLock的解析,而重入锁ReentrantLock
的核心在于它的两个锁非公平锁
和公平锁
的所继承的父类AbstractQueuedSynchronizer
,接下来就是关于AbstractQueuedSynchronizer
的详解,包括图文、源码。后文AbstractQueuedSynchronizer
简称AQS
。
此文相比前文重入锁ReentrantLock多了源码与图解,并且从AQS
的角度出发进行解析。
AQS流程图
源码解析
acquire方法源码
根据上面的AQS
的执行流程图,AQS
先执行acquire()
方法。源码如下:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
- 分别调用了,
tryAcquire()
(需要由子类实现); - 调用
addWaiter(Node.EXCLUSIVE)
以独占模式创建结点Node
,并将当先线程作为Node
的thread
变量,Node
加入到AQS
的队列中; - 以
addWaiter(Node.EXCLUSIVE)
构建好的新Node
参数,调用acquireQueued
方法。
acquireQueued方法源码
线程队列的等待,由acquireQueued
方法进行实现的:
final boolean acquireQueued(final Node node, int arg) { boolean failed = true; try { boolean interrupted = false; for (;;) { final Node p = node.predecessor(); if (p == head && tryAcquire(arg)) { setHead(node); p.next = null; // help GC failed = false; return interrupted; } if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) // 注释1 interrupted = true; } } finally { if (failed) cancelAcquire(node); }
- 此方法对应上图中的圆形循环;
- 判断前置结点
prev
是否为头结点head
; - 不是
head
结点,调用shouldParkAfterFailedAcquire
判断是否应当park
等待,并通过parkAndCheckInterrupt
方法执行LockSupport.park(this)
进行线程的等待,等待已经获得成功的线程release
; - 是
head
结点,则调用tryAcquire
方法,获取成功,则将当前结点设置为头结点head
,线程获取成功,至此acquire
执行完毕。
release释放
public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; }
看上述代码,可以发现,是以head
结点为参数,调用unparkSuccessor
方法:
private void unparkSuccessor(Node node) { /* * If status is negative (i.e., possibly needing signal) try * to clear in anticipation of signalling. It is OK if this * fails or if status is changed by waiting thread. */ int ws = node.waitStatus; if (ws < 0) compareAndSetWaitStatus(node, ws, 0); /* * Thread to unpark is held in successor, which is normally * just the next node. But if cancelled or apparently null, * traverse backwards from tail to find the actual * non-cancelled successor. */ Node s = node.next; if (s == null || s.waitStatus > 0) { s = null; for (Node t = tail; t != null && t != node; t = t.prev) if (t.waitStatus <= 0) s = t; } if (s != null) LockSupport.unpark(s.thread); }
- 设置等待状态为
0 - 初始状态
waitStatus
大于0的状态为CANCELLED
,意为结点取消,需要将大于0的结点从队列中剔除- 最后,调用
LockSupport.unpark(s.thread)
,以让head
的后结点线程取消park
方法的阻塞,对应上面acquireQueued
方法的注释1
处 - 下面是
waitStatus
可能出现的值:
/** waitStatus value to indicate thread has cancelled */ static final int CANCELLED = 1; /** waitStatus value to indicate successor's thread needs unparking */ static final int SIGNAL = -1; /** waitStatus value to indicate thread is waiting on condition */ static final int CONDITION = -2; /** * waitStatus value to indicate the next acquireShared should * unconditionally propagate */ static final int PROPAGATE = -3;
ReentrantLock的公平锁与非公平锁
从AQS的acquire
和release
方法可以看到,都调用了tryAcquire
与tryRelease
方法,这两个方法需要由子类实现。最典型的就是可重入锁ReentrantLock
的NonfairSync
(非公平锁)与FairSync
(公平锁)实现。
acquire方法的入口之lock()方法
非公平锁NonfairSync
:
final void lock() { if (compareAndSetState(0, 1)) setExclusiveOwnerThread(Thread.currentThread()); else acquire(1); }
公平锁FairSync
:
final void lock() { acquire(1); }
可以发现,非公平锁在调用acquire
方法之前,先调用了compareAndSetState
方法,而公平锁是直接调用的acquire
。
非公平锁与公平锁的tryAcquire实现
非公平锁:
protected final boolean tryAcquire(int acquires) { return nonfairTryAcquire(acquires); } final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
公平锁:
protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }
两者都有共同之处:
compareAndSetState
更新状态- 同一线程多次调用
lock
,会累加state
的值,于是也需要对应次数的release
但是公平锁
有一个hasQueuedPredecessors
判断方法:
public final boolean hasQueuedPredecessors() { Node t = tail; // Read fields in reverse initialization order Node h = head; Node s; return h != t && ((s = h.next) == null || s.thread != Thread.currentThread()); }
从源码不难看出,这里的判断逻辑是,头尾结点不相等,头结点的后结点不为空或者头结点的后结点的线程不为当前线程。简单总结一下就是:队列里排在最前面的结点不是当前线程的结点。这也是公平锁的公平体现之处。
那么,非公平锁又是怎样体现的呢,可以从lock
方法看出,在acquire
方法之前,先进行compareAndSetState
抢锁,这个时候有可能set
成功,也有可能失败,失败的话就会进入AQS
队列,然后顺序执行,而它也有可能被其它线程捷足先登,所以它是非公平的。
打个比方:
- 公平锁:排队到食堂吃饭,所有人都严格根据顺序先来后到进行打饭,这个打饭窗口就好比锁,只能按照先来后到的顺序获得锁;
- 非公平锁:同样排队吃饭,假设这个时候已经排起长队了,而后面每个人来的时候都会趁第一个人不注意进行插队,如果被发现了则乖乖地到后面排队,如果没被发现,就插队成功啦。
这篇关于Java并发编程之AbstractQueuedSynchronizer队列同步器与可重入锁ReentrantLock的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23Springboot应用的多环境打包入门
- 2024-11-23Springboot应用的生产发布入门教程
- 2024-11-23Python编程入门指南
- 2024-11-23Java创业入门:从零开始的编程之旅
- 2024-11-23Java创业入门:新手必读的Java编程与创业指南
- 2024-11-23Java对接阿里云智能语音服务入门详解
- 2024-11-23Java对接阿里云智能语音服务入门教程
- 2024-11-23JAVA对接阿里云智能语音服务入门教程
- 2024-11-23Java副业入门:初学者的简单教程
- 2024-11-23JAVA副业入门:初学者的实战指南