ReentrantLock
public void
unlock() {
sync.release(1);
}
释放锁基本过程:
1.线程节点尝试释放锁,state-1,state=n -> state=0,
然后设置当前持有锁节点的线程为null
2.头节点!=null并节点信号量waitStatus!=0,CAS设
置头节点waitStatus==0,唤醒下一个线程节点尝试竞争
锁
abstract void lock();
公平锁
ReentrantLock
public void lock() {
sync.lock();
}
加锁基本过程:
1.线程尝试竞争锁
2.竞争锁失败,线程封装成节点加入CLH双向链表阻塞队
列
3.线程进行自旋(spin,耗费cpu资源),尝试竞争锁,竞
争锁失败,阻塞排队的线程节点,需要等待持有锁的线
程释放锁unpark线层或者其他方式唤醒线程,线程再尝
试获取锁.
abstract void lock();
公平锁
FairSync extends Sync
公平锁 非公平锁
NonFairSync extends Sync
Sync:
1.Sync是ReentrantLock的一个内部类,extends
AbstractQueuedSynchronizer
2.sync.lock():Sync的一个抽象方法:abstract void
lock();
final void lock() {
acquire(1);
}
模板方法,继承
Sync,Sync继承
AQS,通过acquire
实现各自的加锁逻
辑。
子类FairSync重写
的tryAcquire(arg)
方法自定义实现
竞争公平锁逻辑
FairSync NonFairSync
True:返回true,竞
争锁成功,当前线程
为锁持有线程
False:返回false,竞
争锁失败
addWaiter(Node.EX
CLUSIVE)
state==0:非重入
锁,修改
state==1,设置当
前线程为锁持有
线程,线程封装为
节点Node;
重入锁:修改
state+1
线程节点入队
节点模式:独占锁
tryAcquire(arg)
true
false
addWaiter(Node.EX
CLUSIVE)
尾部节点是否
为空
说明该线程节点是
第一个进入CLH队
列排队的,需要初
始化一个空的头部
节点和将当前节点
入队
说明CLH队列中已经有线程节点在
排队了,当前节点需要排在队尾。
构造好node.prev指向前一个节点、
前一个节点的指向当前节点
pred.next
Y
N return node
enq(node)
头部节点
判断头部节点
或者尾部节点
是否为空
初始一个空的头部
节点,第二次的for
循环将当前节点入
队到头部节点之
后,return 头部节
点
Y
直接将当前节点入
队到头部节点之后
(第一次循环就可
以return 头部节点
)
N
head
tail
Node:n1
Thread=null
Prev=null
Next=n2
Node:n2
Thread=t2
Prev=n1
Next=null
头部节点
当前节点入队
当前线程入队之后的效果
acquireQueued
(node,arg)
进入队列的线程节
点进入自旋,尝试获
取锁,竞争不到锁,
线程节点被阻塞
前驱节点p ==
head
true
tryAcquire(arg)
获取锁成功,将当前
节点设置为头部节
点获取到锁的节点
不再排队,return
interrupted,跳出循
环
shouldParkAfter
FailedAcquire(p,
node)
false
返回false:前驱节点p的信号量waitStatus==0,信号量会被修改为-1,
此时返回的是false,线程未进入阻塞,自旋,再次尝试获取锁,但是第
二次由于信号量是-1,返回的是true,线程节点进入阻塞状态,需要等
待持有锁的线程释放锁unpark线层或者其他方式唤醒线程,线程再
尝试获取锁.(队列里所有进入阻塞状态的线程节点信号量都==-1)
true
parkAndCheckIn
terrupt()
阻塞线程节点
true
shouldParkAfterFailedAcquire(p, node)方法详解:
入参介绍:
P:当前节点的前驱节点
node:当前节点
pred.waitStatus
==-1?
retuen true
Y
pred.waitStatus
>0?
N
说明waitStatus==1,
即当前节点的前驱
节点已经是取消状
态了,无法获取锁,
会被移除出CLH队
列
Y
设置前驱节点的
WaitStatus==-1,即
为可获取锁状态
N
为什么这里是设置前驱
节点的waitStatus==-1?
因为NODE.SIGNAL状态表
示的是该节点是可以获
取锁的,所以一旦前驱节
点的信号量都是-1,代表
前驱节点都还没唤醒,所
以需要唤醒前驱节点,那
么当前节点就需要park
公平锁和非公平锁
也许大家会觉得都是需要进入队列,为什么不公平。
实际上进入队列中挂起的线程确实是公平的,最开始进入
直接调用tryAcquire方法,如果获取到锁了就不会进入链
表中,也不会被挂起。而公平和非公平锁的tryAcquire就
一个地方不同,公平锁多了hasQueuedPredecessors方法,
公平锁会判断链表中是否有其他线程在等待,如果有,就
会把自己也加入到链表末尾,而非公平锁没有这个判断,
直接是尝试获取锁,而正当锁被释放,有一个新的线程
调用了lock方法这就会与链表中被唤醒的线程形成竞争关
系,所以就成了非公平。
问题:不是说还有非公平锁吗,这里为什么还要等
前驱节点先获取锁呢?
判断
compareAndSet
State(0, 1)是否
成功
tryAcquire()-
>nonfairTryAcquire()
true
非公平方式获
取锁
获取锁成功,返回
true
true
获取锁失败,返回
false
false
fasle,线程自旋,
尝试获取锁,
获取锁失败,进入阻塞
final void lock(){...}
tryRelease(int
releases)
释放锁成功?
return false
N
头部节点h !=
null?
Y
头部节点为空,不存
在下个线程需要被
唤醒,释放锁成功,
返回true
false
h.waitStatus !=
0?
True
所有队列的线程的
信号量==0,表示不
存在下个线程需要
被唤醒(只有==-1
才需要唤醒),释放
锁成功,返回true
unparkSuccessor()
true,唤醒下个线程
为什么唤醒下个线程需要从队列尾部遍历?
1.先获取头部节点的下个线程节点,找不到才从尾部遍历
2.头部节点的信号量》0,即waitStatus==1,为CANCEL状态,需要从队列中剔除该节点,属于无效
节点
既然选择了从尾部往头部遍历,那解决的问题呢?
高并发场景下,线程节点入队方法enq()出现了线程安全问题。
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
//队列为空需要初始化,创建空的头节点
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
//set尾部节点
if (compareAndSetTail(t, node)) {//当前节点置为尾部
t.next = node; //前驱节点的next指针指向当前节点
return t;
}
}
}
}
原子问题:
当前节点置为尾部compareAndSetTail(t, node)使用了CAS保证线程安全,但是if括号里面的语句并
没有任何手段保证线程安全。
高并发下,假设线程1到 if (compareAndSetTail(t, node)) {,刚好将当前节点置为尾部,cpu时间片
用完了,上下文切换,线程挂起,此时线程2进来了,并且成功执行了方法。此时线程3释放
锁,执行了unparkSuccessor()方法,加入是从头部开始寻找下个节点,此时的线程1的next确是
null,存在后续节点丢失的情况。
此时发生上下文切换,时间片交由线程C,线程C调用了unparkSuccessor方法,假如是从头到尾
的遍历形式,在node2就会发现,next指针为null,似乎没有后续节点了。
此时发生上下文切换,时间片交由线程A,A将node2的next=node3。奇怪的现象发生了:对于线
程C来说,后续没有node3和node4,但是对于其它线程来说,却出现了这两个节点。
注意:我们将的公平锁和非公平锁跟队列里面的线程是否公平两码事,并且队列里的线程是非
公平的。
false
评论0
最新资源