- 1.前言
- 2.ReentrantLock介绍
- 3.AQS
- 3.1主要原理
- 3.2数据结构
- 3.3不同模式加锁过程
- 3.4 自定义同步器中需要实现的重要方法
- 4 ReentrantLock中的AQS分析
- 4.1加锁过程
- 4.1.1第一步:尝试获取锁(利用cas更改state值)
- 4.1.2第二步:线程加入队列
- 4.1.3第三步:队列中的节点自旋并阻塞
- 4.2解锁过程
- 4.2.1 第一步:资源释放
- 4.2.2 第二步,如果资源全部释放则唤醒阻塞的线程
- 4.3 Condition 实现原理
- 4.3.1 condition队列数据结构
- 4.3.2 await方法与signal方法执行逻辑
- 4.1加锁过程
- 参考文章
AQS位于java.util.concurrent.locks包下,全名 AbstractQueuedSynchronizer,本质上是一套同步器框架,java中许多线程同步工具都是基于它的。
工具
是什么
ReentrantLock
ReentrantLock是可重入的排它锁,同一时刻仅有一个线程可以进行访问
ReentrantReadWriteLock
ReentrantReadWriteLock维护着一对锁,一个读锁,一个写锁,
同一个时间,允许多个线程同时获取读锁
CountDownLatch
使用给定计数初始化CountDownLatch,由于调用countDown方法,在当前计数到达0之前,await方法会一直阻塞,
之后会释放等待线程计数无法被重置
Semaphore
信号量Semaphore是一个控制访问共享资源的计数器,信号量初始化许可的个数即是当前允许线程同时访问总的个数。
在获取许可前,会阻塞acquire的线程。每一个release会添加一个许可,并可以释放一个正在阻塞的获取者。
本篇将以使用最多的ReentrantLock为例,进行AQS原理分析
在正式进入介绍之前先理解一下几个常见的锁概念:
公平锁与非公平锁
公平锁:公平锁中,锁的获取顺序是按照先到先得的原则进行的。实现上,某个线程竞争锁时,只要锁的等待队列不为空,则该线程需要阻塞插入队列尾部,获取竞争锁的机会
非公平锁:后到的新线程可以直接竞争锁,只有在竞争锁失败时,才会被阻塞插入队列尾部。
共享锁与排他锁
共享锁与排他锁最主要的区别就是:排他锁在同一时刻只能被同一个线程占有,而共享锁可被多个线程持有。
可重入锁
可重入锁支持被同一个线程多次获取,即当一个线程获取锁之后,这个线程还可以再次获取锁
我们通过AQS都可以实现上述的公平锁,非公平锁,共享锁,排他锁,可重入锁,那么怎么实现呢,稍后一一介绍
2.ReentrantLock介绍ReentrantLock是一种可重入排他锁
类图:
如上图,ReentrantLock基于Sync内部类实现了Lock接口,其中Sync实现了AQS,提供了NonfairSync和FairSync 2种实现。
ReentrantLock与Synchronized区别
特征
ReentrantLock
Synchronized
功能支持响应中断,超时,尝试获取不支持锁类型公平锁,非公平锁非公平锁实现AQSjvm监视器重入性可重入可重入解锁手动解锁自动解锁非公平锁的加锁流程
final
void
lock() {
if
(compareAndSetState(
0
,
1
))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(
1
);
}
通过CAS设置status
- 如果成功的话,说明获取锁成功,并将独占线程设为当前线程
- 失败的话,说明获取锁失败,调用acquire(1)
公平锁的加锁流程
final
void
lock() {
acquire(
1
);
}
公平锁中,直接调用acquire(1)方法
公平锁与非公平锁的解锁流程是一样的
public
void
unlock() {
sync.release(
1
);
}
由上可以看出,在加锁流程中,不论是公平锁还是非公平锁,都调用了acquire()方法以及解锁流程中调用的release方法,正是在AQS中定义的核心方法
3.AQS 3.1主要原理AQS主要数据结构为双向链表+state
主要原理如下:
线程1请求获取资源state空闲,将线程1设置为工作线程,并锁定资源。而其他线程请求获取资源时发现资源被占用,AQS就将请求资源的线程阻塞并封装成节点入FIFO队列(尾部入队列,头部出队列),当线程1执行完毕,完全释放所有资源后,AQS会唤醒线程2,使得线程2获得竞争资源的机会,以此往复。。。
3.2数据结构这里的数据结构,是指队列中的节点
类型
属性
描述
volatile Nodeprev前驱节点
volatile Nodenext后继节点
volatile Threadthread处于该节点的线程
volatile int
waitStatus
节点在队列中的状态
CANCELLED 1:表示由于线程超时或中断导致的线程获取锁的请求已经取消了
SIGNAL -1:线程准备好,等待资源释放
CONDITION -2:节点在condition队列中,节点线程等待唤醒
PROPAGATE -3:线程处于SHARED(共享模式)时,会使用
0:Node被初始化的默认值
NodenextWaiter返回处于CONDITION状态的节点SHARED /EXCLUSIVE
SHARED :共享模式等待锁
EXCLUSIVE:独占模式等待锁
volatile intstate临界资源 3.3不同模式加锁过程
方法名称
描述
tryAcquire(int)
独占方式,尝试获取资源,成功返回true,失败返回false
tryRelease(int)
独占方式,尝试释放资源,成功返回true,失败返回false
tryAcquireShared(int)
共享方式,尝试获取资源
tryReleaseShared(int)
共享方式,尝试释放资源
其中tryAcquire-tryRelease是一对、tryAcquireShared-tryReleaseShared是一对,可以只实现其中的一对,如ReentrantLock是独占锁,所以实现了tryAcquire-tryRelease
以非公平锁为例进行代码分析
如果成功则获取锁,失败则进行acquire(1)
static
final
class
NonfairSync
extends
Sync {
...
final
void
lock() {
//直接尝试获取锁,
if
(compareAndSetState(
0
,
1
))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(
1
);
}
...
}
获取锁失败
public
final
void
acquire(
int
arg) {
if
(!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
非公平锁中tryAcquire()实现,如果返回true则获取锁,返回false,则需要加入队列
protected
final
boolean
tryAcquire(
int
acquires) {
return
nonfairTryAcquire(acquires);
}
final
boolean
nonfairTryAcquire(
int
acquires) {
final
Thread current = Thread.currentThread();
int
c = getState();
//c==0说明队列为空
if
(c ==
0
) {
//尝试独占资源
if
(compareAndSetState(
0
, acquires)) {
setExclusiveOwnerThread(current);
return
true
;
}
}
//可重入锁逻辑,判断独占资源的线程是否就是当前的线程
else
if
(current == getExclusiveOwnerThread()) {
int
nextc = c + acquires;
if
(nextc 0说明是取消状态,循环向前查找,把取消的节点删除
if
(ws >
0
) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do
{
node.prev = pred = pred.prev;
}
while
(pred.waitStatus >
0
);
pred.next = node;
}
else
{
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//这时waitStatus 只剩下0或者PROPAGATE
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return
false
;
}
阻塞
private
final
boolean
parkAndCheckInterrupt() {
LockSupport.park(
this
);
return
Thread.interrupted();
}
ReentrantLock在解锁的时候,并不区分公平锁和非公平锁
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
;
}
//tryRelease中资源全部释放返回true,否则返回false
protected
final
boolean
tryRelease(
int
releases) {
//减少可重入次数
int
c = getState() - releases;
if
(Thread.currentThread() != getExclusiveOwnerThread())
throw
new
IllegalMonitorStateException();
boolean
free =
false
;
//如果资源占用全部释放,则将独占线程释放
if
(c ==
0
) {
free =
true
;
setExclusiveOwnerThread(
null
);
}
setState(c);
return
free;
}
public
final
boolean
release(
int
arg) {
if
(tryRelease(arg)) {
Node h = head;
//
if
(h !=
null
&& h.waitStatus !=
0
)
unparkSuccessor(h);
return
true
;
}
return
false
;
}
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
) {
s =
null
;
for
(Node t = tail; t !=
null
&& t != node; t = t.prev)
if
(t.waitStatus
0
|| !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return
true
;
}
待续。。。参考文章
https://segmentfault.com/a/1190000016462281
从ReentrantLock的实现看AQS的原理及应用 - 美团技术团队