定义
重入锁是一种递归无堵塞的同步机制。类似于synchronized,但是比synchronized更加的灵活,可自由选择加锁的位置。它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器加1,并且锁需要被释放两次才能真正获得释放。
重入锁提供了两种加锁方式,公平锁以及非公平锁。默认是非公平锁。
公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这个方式肯呢过造成某些线程一直拿不到锁,结果也就是不公平的了。
常用的加锁方法
-
lock():
- 如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
- 如果当前线程已经保持该锁,则将保持计数加 1,并且该方法立即返回。
- 如果该锁被另一个线程保持,则出于线程调度的目的,禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态,此时锁保持计数被设置为 1
-
lockInterruptibly()
- 如果当前线程未被中断,则获取锁。
- 如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
- 如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回。
- 如果锁被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态(锁由当前线程获得;或者 其他某个线程中断当前线程。 )
- 如果当前线程获得该锁,则将锁保持计数设置为 1。
- 此方法是一个显式中断点,所以要优先考虑响应中断,而不是响应锁的普通获取或重入获取。
-
tryLock():仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
- 如果该锁没有被另一个线程保持,并且立即返回 true 值,则将锁的保持计数设置为 1。
- 如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true。
- 如果锁被另一个线程保持,则此方法将立即返回 false 值。
源码解析
lock()
ReentrantLock是在AQS的基础上实现的独占锁。
- 尝试加锁
final void lock() {
// 检测锁的状态,如果满足条件,则将当前锁的拥有者设置为为当前线程
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
- 非公平锁方式下尝试获取锁
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
// 获取当前锁的状态
int c = getState();
//如果状态为0,说明当前未有线程占有锁
if (c == 0) {
// CAS操作,
if (compareAndSetState(0, acquires)) {
// 设置锁的拥有着为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//重入的逻辑
// 同一线程再次获取锁
else if (current == getExclusiveOwnerThread()) {
// 将锁的状态增加1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
- 锁的释放
public final boolean release(int arg) {
// 尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 当头节点不为null以及该节点未被取消的情况下,唤醒线程
if (h != null && h.waitStatus != 0)
//线程唤醒
unparkSuccessor(h);
return true;
}
return false;
}
- 具体的释放逻辑
protected final boolean tryRelease(int releases) {
//将当前锁的状态减1
int c = getState() - releases;
//如果占有锁的当前线程不是该线程则抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 锁的当前状态为0,即锁释放完成(考虑同一根线程加锁两次,需要释放两次)
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//设置锁的状态
setState(c);
return free;
}
- 公平锁与非公平锁的区别
public final boolean hasQueuedPredecessors() {
Node t = tail;
Node h = head;
Node s;
// 通过判断"当前线程"是不是在CLH队列的队首,来返回AQS中是不是有比“当前线程”等待更久的线程
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
lockInterruptibly()
与lock的区别在与如下方法
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 此处,lock只是返回中断状态,而lockInterruptibly则抛出异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryLock
与lock的区别在于在未获得锁的情况下,并未进行入等待队列的操作,而是直接返回结果。
小结
总的来说,只要理解AQS后,重入锁的相关实现也就很简单了。