ReentrantLock类结构
- 该类实现Lock接口。Lock接口定义了锁最基本的方法。
- 该类的final成员变量Sync同步器,以静态内部类的形式提供了公平同步器和非公平同步器。(构造器提供选择是否公平,默认非公平。公平锁性能要劣于非公平锁)
Sync类结构
- Sync接口继承了AbstractQueuedSynchronizer抽象类,也就是我们常听说的AQS。
- AbstractOwnableSynchronizer抽象类,提供设置获取锁的当前线程的方法(便于互斥和可重入功能)。
LOCK方法(非公平锁为例)
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)) // 原子实现状态的设置(内部调用unsafe的方法)。0 -> 1
setExclusiveOwnerThread(Thread.currentThread()); // 设置获锁的线程
else
acquire(1); // 尝试获锁,排队
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Sync的原理很简单就是维护一个volatile的status的值,使用CAS的方式来实现状态改变原子操作。
public final void acquire(int arg) {
if (!tryAcquire(arg) && // 再次尝试获取锁
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) // 失败后排队
selfInterrupt(); // 自断
}
对于没有立刻获取锁的线程,会加入到等待队列当中。
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread(); // 当前线程
int c = getState(); // 当前锁状态,0没人持有
if (c == 0) {
// 获取锁
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;
}
通过判断当前线程是否为持锁的线程,如果是的话通过改变Staus的值(+1)来实现可重入锁的功能,因为只有一个线程,设置state不需要原子操作。
看一下排队代码:
private Node addWaiter(Node mode) { Node.EXCLUSIVE
Node node = new Node(Thread.currentThread(), mode); // 构建节点
// Try the fast path of enq; backup to full enq on failure // 这种方式快一点,,先CAS,失败在enq
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) { // CAS 保证原子性
pred.next = node;
return node;
}
}
enq(node); // 头节点或其他CAS失败的,该方法使用for死循环直到入队成功
return node;
}
自旋队列获取锁的过程:
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)) { // 尝试CAS 获取锁(原因:1.FIFO,2.头节点是获取锁的,释放后会唤醒下一节点)
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && // 如果节点状态是SIGANL但是没有获取锁可以安全挂起当前线程,如果是退出节点忽略,获取未推出的前继节点再尝试获取,如果状态是可以获取锁,将节点状态改为SIGNAL,再尝试获取锁
parkAndCheckInterrupt()) // 检测该线程是否中断
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
这里符合FIFO,乍一看可能觉得挺公平的。但是非公平锁是先CAS获取锁,没有获取后才会去排队。因此刚释放锁的线程还是有机会去获取锁。所以对于公平锁来将要实现公平需要满足队列FIFO,但是直接排队采取一个一个获取锁方法又不是太可取,是怎么做的呢,看一下源码:
可以看到代码只比非公平锁多了一个判断是否有前继节点的方法hasQueuedPredecessors(),这样保证每次获取锁的时候就会先让排在队头的节点获取锁。当然这样肯定会造成线程上下文切换次数变多,吞吐量降低。
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread()); // true: 队列不为空,头节点的下一个节点为null(只有第一次入空队才会出现enq()方法,说明当前线程晚于一个线程进入acquire(1)方法,之后也需要排队),或者下一节点不是当前线程,说明有前继节点。
}
UNLOCK方法
锁的释放就很简单,直接看一下源码:
unparkSuccessor(h)方法用于唤醒后继节点的线程。
其他方法tryLock
Boolean tryLock(): 直接调用sync的CAS方法,返回是否获取到锁
tryLock(longtimeout,TimeUnitunit): 先调用CAS方法,没有获取锁,再调用带有过期时间的CAS方法(采用LockSupport.parkNanos指定挂起指定时间,这段时间都没有被唤醒获取锁的话返回false)