ReentrantLock详解
非公平锁的lock方法

1.当我们调用ReentrantLock的lock方法的时候,实际上是调用了NonfairSync的lock方法,这个方法先用CAS操作,去尝试抢占该锁。如果成功,就把当前线程设置在这个锁上,表示抢占成功。如果失败,则调用acquire模板方法,等待抢占。
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
2.其中acquire的原理是尝试获取锁,如果没有获取成功,就在AQS队列中增加一个当前线程的结点,表示等待抢占。在进入队列时会去再次执行一次获取锁操作,如果没有获取就挂起等待唤醒。
tryAcquire(arg)方法就是尝试再次获取锁,先比较当前锁的状态是否是0,如果是0,则尝试去原子抢占这个锁,如果当前锁的状态不是0,就去比较当前线程和占用锁的线程是不是一个线程,如果是,会去增加状态变量的值,其次返回false。
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
3.结点加入队列,进入acquireQueued方法,外层是一个for循环,如果当前结点是头结点的下个结点,再通过tryAcquire尝试获取锁,如果获得,说明头结点已经释放锁,这时将当前结点设置为头结点。将上一个结点的next设置为null。如果本次循环没有获取到锁,则进入shouldparkAfterFailedAcquire方法,此方法会判断当前线程是否挂起,如果前一个结点已经是SIGNAL状态,则需要挂起,如果前一个结点是取消状态,则删除结点。如果是其他状态,则尝试设置成SIGNAL状态,并返回不需要挂起,进行第二次抢占。完成上面后进入挂起状态。
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())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
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.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
unlock方法详细描述
1、调用unlock方法,其实是直接调用AbstractQueuedSynchronizer的release操作。
2、进入release方法,内部先尝试tryRelease操作,主要是去除锁的独占线程,然后将状态减一,这里减一主要是考虑到可重入锁可能自身会多次占用锁,只有当状态变成0,才表示完全释放了锁。
3、一旦tryRelease成功,则将CHL队列的头节点的状态设置为0,然后唤醒下一个非取消的节点线程。
4、一旦下一个节点的线程被唤醒,被唤醒的线程就会进入acquireQueued代码流程中,去获取锁。
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;
}
synchronized 和 ReentrantLock 区别
参考博文:https://blog.csdn.net/meism5/article/details/90413901
1.synchronized 竞争锁时会一直等待;ReentrantLock 可以尝试获取锁,并得到获取结果
2.synchronized 获取锁无法设置超时;ReentrantLock 可以设置获取锁的超时时间
3.synchronized 无法实现公平锁;ReentrantLock 可以满足公平锁,即先等待先获取到锁
4.synchronized 控制等待和唤醒需要结合加锁对象的 wait() 和 notify()、notifyAll();ReentrantLock 控制等待和唤醒需要结合 Condition 的 await() 和 signal()、signalAll() 方法
5.synchronized 是 JVM 层面实现的;ReentrantLock 是 JDK 代码层面实现
6.synchronized 在加锁代码块执行完或者出现异常,自动释放锁;ReentrantLock 不会自动释放锁,需要在 finally{} 代码块显示释放
本文深入探讨了ReentrantLock的工作原理,包括非公平锁的lock方法实现,与synchronized关键字的区别,以及解锁流程。详细解释了NonfairSync的lock方法如何使用CAS尝试获取锁,acquire方法在获取锁失败时如何将线程加入AQS队列,以及unlock方法如何释放锁并唤醒等待线程。

1011

被折叠的 条评论
为什么被折叠?



