是java 实现的公平锁/非公平锁,也是可重入锁
AbstractQueuedSynchronizer
) 什么关系AbstractQueuedSynchronizer
翻译过来是抽象队列同步 是不是意味着aqs
内部维护中一个队列。 1.下图是 ReentrantLock
内部实现 公平锁FairSync
跟aqs
的关系
2.下图是 ReentrantLock
内部实现 公平锁NonfairSync
跟aqs
的关系
3.aqs
内部维护的队列是什么样子的
简单看下aqs
内部队列的实现
static final class Node {
//该节点的上一个节点
volatile Node prev;
//该节点的下一个节点
volatile Node next;
//持有该节点的线程
volatile Thread thread;
}
可以看出来aqs
内部维护着一个Node
有点像LinkedList
,所以aqs
底层准确来说是由一个链表实现的
4.aqs
出了有个内部类是Node
外,aqs
这个类属性还维护着链表的头节点和尾节点
//头节点
private transient volatile Node head;
//尾节点
private transient volatile Node tail;
ReentrantLock
是怎么锁住的?有什么标识说已经上锁了嘛ReentrantLock
等待获取锁是怎么实现的,等待的线程在干嘛,之后怎么获取到锁ReentrantLock
可重入锁怎么实现的ReentrantLock
公平锁跟公平锁的实现有什么不同带着问题看源码
简单示例
public static void main(String[] args) {
//ReentrantLock 默认是非公平锁, new ReentrantLock (true);
//构造方法传入 true 表示 公平锁
ReentrantLock lock = new ReentrantLock (true);
Thread t1 = new Thread(() -> {
lock.lock();
System.out.println("t1 lock ing");
Thread.sleep(100000000);
lock.unlock();
});
t1.start();
Thread t2 = new Thread(() -> {
lock.lock();
System.out.println("t2 lock ing");
Thread.sleep(100000000);
lock.unlock();
});
t2.start();
Thread t3 = new Thread(() -> {
lock.lock();
System.out.println("t3 lock ing");
Thread.sleep(100000000);
lock.unlock();
});
t3.start();
}
//true 表示公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
ReentrantLock
上锁过程以上面的demo为列子,分为三种常见
直接到公平锁的实现 lock
方法,直接调用 acquire(1)
注意 参数是 1 ,这个参数什么意思?表示上锁一次
final void lock() {
acquire(1);
}
acquire()
// 此时 arg = 1
public final void acquire(int arg) {
//1.tryAcquire 尝试加锁,当返回false也就是加锁失败执行
//acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
//2.addWaiter(Node.EXCLUSIVE) 加锁失败之后,将该节点添加到等待队列
//3.再次尝试获取锁,获取不到锁就等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
//aqs的方法,aqs的属性 state 此时0表示没有加锁状态
//state>0 表示上锁的次数(重入锁会大于1)
int c = getState();
//此时t1过来,第一次,所以肯定是0,锁处于自由状态
if (c == 0) {
//1.判断队列是否还有在排队的,如果没有走 compareAndSetState
//2.compareAndSetState 比较交换state 这就是上锁,通过cas修改state的值
//3.setExclusiveOwnerThread 设置当前持有锁的线程
//exclusiveOwnerThread 这个属性是aqs维护的表示当前持有锁的线程对象
//4.t1过来 hasQueuedPredecessors = false 因为当前队列中没有节点
//所以就执行2跟3获取到锁,但是假如现在t1获取到锁,t2过来了 这个if就进不去了
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//t2上面的if进不去,就会走这个if
//current == getExclusiveOwnerThread() t2的线程跟当前持有锁的线程肯定不
//相等,因为现在持有锁的线程是t1
//所以这个if什么时候会进入呢,这个就是可重入锁的原理
//当t1在持有锁的时候再次调用lock此时线程=当前持有锁的线程
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
//设置state 也就是往sate加1
setState(nextc);
return true;
}
return false;
}
}
总结:tryAcquire
1.会尝试获取锁,通过判断当前队列头节点是否是当前想要获取锁的线程,如果是就尝试获取锁。2.如果获取到锁就做两件事情,a.通过cas
修改state
标志为上锁 b.修改aqs
当前持有锁的线程对象 3.判断是否重入锁,如果重入锁通过cas
将 state
加一 4.如果获取到锁返回true
否则返回false
addWaiter(Node.EXCLUSIVE)
当获取不到锁就会走addWaiter(Node.EXCLUSIVE)
的逻辑,所以t1是不会走addWaiter(Node.EXCLUSIVE)
t2,t3就会走这个逻辑
private Node addWaiter(Node mode) {
//新建一个持有当前线程的节点
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//tail是尾节点,没有初始化,所以t2进来的时候tail = null,所以t2不走下面逻辑
//但是t3过来的时候,tail已经被t2初始化了(下面enq做的)所以t3会走下面的逻辑
if (pred != null) {
//t3过来此时尾节点是t2,设置t3节点的前节点为t2
node.prev = pred;
//通过cas修改t3的node为尾节点
if (compareAndSetTail(pred, node)) {
//将t2的下一个节点设置成t3,到这里一个链表就形成了
//head node -> node(t2)->tail node(t3)
pred.next = node;
return node;
}
}
//t2会走这里,t1会加锁成功,不会走这个,t3在上面形成链表之后return出去了
enq(node);
return node;
}
enq(node)
private Node enq(final Node node) {
//死循环
for (;;) {
//t2第一次循环的时候 tail = null
Node t = tail;
if (t == null) { // Must initialize
//cas 设置 head = new Node()
//tail = head
if (compareAndSetHead(new Node()))
tail = head;
} else {
//t2进来第二次循环,这时候tail = head
//所以走这个逻辑
node.prev = t;
//cas将t2设置成tail,head的next指向t2 head ->node(t2)
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
总结:1.当t2进来addWaiter
的时候,tail=head=null
,所以会走enq
方法去初始化tail=head=new Node()
,将head.next = Node(t2)
,此时的head
其实是空的,就是一个摆设 2.当t3过来 addWaiter
的时候 因为t2初始化了tail
所以走if
的逻辑,设置Node(t3)= tail,形成 head Node -> Node(t2)->tail Node(t3)的链表 3.注意链表是双向的 4.到这里已经将t2,t3加入链表也就是aqs
的队里
acquireQueued
final boolean acquireQueued(final Node node, int arg) {
//是否失败的标识
boolean failed = true;
try {
//线程是否被打断的标识
boolean interrupted = false;
//又是死循环
for (;;) {
//拿该节点的上一个节点
final Node p = node.predecessor();
//判断该节点上一个节点是否头节点,如果是头节点就tryAcquire尝试获取锁
//t2过来的时候 刚好满足所以p==head 所以会走 tryAcquire 尝试获取锁
//但是现在锁被t1持有,获取失败,走下面的逻辑
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//t2和t3都会走这个逻辑
//1.shouldParkAfterFailedAcquire 判断是否可以park线程
//通过该节点的上一个节点的 waitStatus == -1 表示该节点可以park
//waitStatus初始化等于零,所以刚开始进去 shouldParkAfterFailedAcquire 这个方法
//会把上一个节点的 waitStatus 改成-1,下一次循环走到这里满足条件了
//2.parkAndCheckInterrupt 线程park,也就是暂停线程,等待获取锁资源
//3.后续解锁就从这里开始走
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结:到这里 ReentrantLock
加锁过程大概完了,acquireQueued
这个方法主要是再去尝试获取锁,如果获取不到锁,就将线程park。这里三个细节点 1.什么时候会走tryAcquire
再次尝试获取锁,当上一个节点是头节点的时候 2.线程park的条件是什么,是上一个节点的 waitStatus
= -1 3.上一个节点的 waitStatus
什么时候会变为-1,一开始时候是0,当进入acquireQueued
的死循环,第一次shouldParkAfterFailedAcquire
在这个方法把 waitStatus
改为-1
ReentrantLock
是怎么锁住的? 是通过aqs
的state属性来标识的,=0标识没有被持有锁,大于1标识被持有锁ReentrantLock
等待获取锁是怎么实现的,等待的线程在干嘛,之后怎么获取到锁?线程会先在去尝试获取锁,获取不到就会进行park,之后被unpark然后重新走 acquireQueued
for循环去尝试获取锁ReentrantLock
可重入锁怎么实现的?aqs
的state值大于1,解锁的时候也会有对应的操作ReentrantLock
公平锁跟公平锁的实现有什么不同?非公平锁一进去lock
方法就会调用compareAndSetState
去尝试获取锁。t1
t1得时候head=tail=null
,获取锁之后修改state
和exclusiveOwnerThread
t2
t2得时候锁还没释放,新建一个Node = head = tail
,新建自己得Node
t2Node,此时head.next = t2Node
t3
t3来的时候锁还没释放,新建t3Node
,t3Node.pre = t2Node
tail = t3Node
,同时修改t2Node得waitStatus
表示t3可以park
了
acquireQueued
自旋,park
线程,等待被唤醒
未完待续...........
作者:三餐吃到饱1
链接:https://juejin.im/post/5f14f3336fb9a07ea929edad