构造方法
- 内部维护了
**Sync**
内部类,Sync继承自**AQS**
,ReentrantLock通过Sync来实现同步工具类 - 在无参构造时,默认为的Sync实现类为
**NonfairSync**
即默认为非公平锁实现
非公平锁获取锁
没有竞争时
- 在调用
**reentrantLock.lock()**
时,本质是调用的**Sync**
中的**lock**
方法 - 抢占锁的过程就是通过
**compareAndSetState(0, 1)**
- 在没有竞争时第一个线程来抢占锁,此时锁的状态为0,因此该线程可以成功抢占到锁
- 此时该线程会将该锁的持有线程设置为自己
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
@ReservedStackAccess
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
当出现锁竞争时
tryAcquire
- 在
**Sync**
中的lock方法中,如果 CAS获取锁失败,则会进入到**acquire()**
中,Sync是继承自AQS的,acquire是AQS提供的方法。 - 在
**tryAcquire()**
方法中会**继续尝试获取锁**
,在第一次获取锁的CAS操作失败可能有两种原因 - 第一种原因,该锁在当时
**被其他线程所占有**
,但在现在**可能该线程已经将锁释放**
,此时会再次尝试CAS获取锁,如果获取锁成功则锁的持有线程设置为自己 - 第二种原因,该锁被自己线程占有,所以state不为0,此时需要判断持有锁的线程是否是自己,如果是自己需要将state+1
@ReservedStackAccess
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
addWaiter和enq
**addWaiter**
的方法作用是在当前线程争抢锁失败后,将该**线程包装成一个Node节点**
- 并且将该Node节点通过
**CAS**
的方式添加到CLH**队列的尾部**
- 在多线程环境下添加队列可能会出现竞争情况,如果一次CAS添加没有成功,就会调用进入到enq方法
- enq方法通过
**自旋+CAS**
的方式,确保该线程所在的Node节点成功添加到CLH队列尾部
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;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}
- enq方法中会自旋操作,在第一次进入enq方法时,head和tail都指向null
- 此时会先调用 compareAndSetHead(new Node()) ,此时head节点会指向哨兵节点,同时将 tail指向哨兵节点
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;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
acquireQueued
- 进入
**acquireQueued**
方法后会**自旋**
获取锁,当且**仅当该节点为第一个节点**
时才能尝试获取锁 - 如果该
**节点不是第一个节点或没有争抢到锁**
,需要调用**shouldParkAfterFailedAcquire**
方法,找到**真正的首节点**
后,将其改为**SIGNAL**
状态,然后该线程会被**LockSupport.park(this)**
挂起
- 线程由挂起状态被唤醒,因为是使用的
**Park**
挂起,此时该线程仍存在中断标记,但此时需要使用
**Thread.interrupted()**
来清除中断标记,否则在自旋获取锁的过程中,如果该线程在 tryAcquire()中**争抢锁失败**
,此时再次被 **LockSupport.park(this)**
挂起时,会因为该线程**已经存在中断标记而无法正常挂起**
- 这样会导致进入
**死循环空转**
的情况,可能会出现CPU100%的情况,因此需要用**interrupted**
变量记录下线程被打断,在**acquire**
方法中根据此处的返回值,将**中断标记重新赋值**
。
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);
}
}
//该方法的返回值为true 说明该线程应当被park挂起
//否则不应当被挂起 应当继续自旋获取锁
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前置节点的状态
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//如果前置节点的状态已经挂起 则不需要进行处理
return true;
if (ws > 0) {
//线程的状态仅有cancel是大于0的
//会一直向前寻找到正常状态的Node节点
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;
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
释放锁
release
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
tryRelease
- 先对基础信息进行校验,只有
**持有锁的线程**
才能对锁做锁释放操作 - 因为锁是可重入的,state为
**锁的重入次数**
,当且仅当state为0后,才会真正进行锁的释放操作 - 锁的释放操作会先将锁的持有线程设置为
**null**
,然后再将锁的state设置为0 - 状态设置和线程变量设置顺序不能交换,因为state有
**Volatile**
修饰,可以保证state修改前语句的可见性
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
唤醒线程
- 在唤醒线程时会唤醒
**哨兵节点的下一个节点**
,如果头节点的下一个节点**为空或是过期节点**
,则需要从后往前进行查询,最靠近哨兵节点的挂起节点。 - 在
**addWait()**
时,尾插法插入,先操作的是**prev**
指针,后操作上一个节点的**next**
节点,可能由于**cpu时间片**
的问题,节点已经插入,prev已经修改了指向,但next节点还没有修改指向 - 当节点取消时
**tryRelease()**
时,先修改的是**prev**
指针,因此需要以prev指针为准 - 从而唤醒线程需要从tail节点往head节点进行查询,即
**从后往前进行查询**
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
LockSupport.unpark(s.thread);
}
锁竞争
- 在
**ReentrantLock**
中,因为是**独占锁**
,只有该节点的前驱节点是**哨兵节点**
时才会被唤醒,此时会在**acquireQueued**
方法中自旋获取锁 - 如果此时成功通过
**tryAcquire**
获取到锁,则会将当前**线程所在的node节点**
代替原先的**哨兵节点**
,同时将原来的哨兵节点从CLH链表中删除 - 如果此时有其他的线程也在通过
**tryAcquire**
一起竞争锁,并且该线程没有成功获取锁,则会继续park挂起该线程,等待持有锁的线程释放锁
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);
}
}
锁的可重入
- 加锁时,在调用
**tryAcquire**
时,如果判断出持有锁的线程是当前线程,会将**state++**
- 释放锁时,在调用
**tryRelease**
时,如果判断出持有锁的线程是当前线程,会将**state--**
,如果此时**state为0**
,才会真正释放锁
非公平锁与公平锁
ReentrantLock默认
- 在
**ReentrantLock**
的默认实现中,是使用的**非公平锁**
(NofairSync)实现,因为**非公平锁的效率比公平锁更高**
- 因为在一个线程
**释放锁时**
,才会通过**unpark**
的方式唤醒CLH队列中**哨兵节点后的第一个节点**
,**unpark**
操作需要切换到内核态,是非常耗时的操作 - 在唤醒线程的过程中,如果此时有其他的线程也来争抢锁,则可以直接获取到锁
- 如果是
**公平锁**
实现,只能由线程释放锁后唤醒CLH队列的节点争抢锁,则都需要**等待unpark节点**
的时间,因此非公平锁的效率要高于公平锁
非公平锁
- 在非公平锁中,任意线程在调用
**ReentrantLock.lock()**
时,都会**先尝试CAS获取锁**
,无论CLH队列中是否有元素正在等待 - 因此在CLH队列中
**被唤醒的元素**
,在尝试tryAcquire获取锁时,仍然会**有其他的CLH队列以外的其他线程**
正在尝试获取锁
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
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);
}
}
公平锁
- 在
**公平锁**
的实现中,线程在调用ReentrantLock.lock()时,**不会先调用CAS尝试获取锁**
,都会**调用acquire获取锁**
- 在FairSync的
**tryAcquire**
实现中,在state为0时,此时没有线程占有锁,**不会直接通过CAS**
修改state获取锁 - 而是会先通过
**hasQueuedPredecessors**
判断CLH队列中是否含有元素,如果队列中仍然存在元素,则不会通过CAS获取锁 **tryAcquire**
争抢锁失败后,后续会通过**addWaiter**
和**acquireQueued**
方法加入到CLH队列中- 并且从CLH队列中唤醒的元素,没有外界的线程与其争抢,因此保证了线程获取
**锁的顺序性**
,体现了公平锁的特点
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
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());
}
条件变量原理
每一个条件变量对应着一个ConditionObject,内部维护着一个双向队列
await
- 当线程0持有锁时,并且调用await方法,此时会在ConditionObject中创建出Node节点,并且加入到双向链表中
- 此时要调用AQS中的 fullyRelease 流程,释放掉所有的锁,因为该线程持有的可能是可重入锁
- 当线程0的锁释放掉后,此时会唤醒队列中的第一个节点,同时与外部的线程进行锁竞争。
signal
- 此时线程1要来唤醒线程0,则会取出ConditionObject中的第一个节点
- 然后将该节点加入到队列的末尾中,同时将该节点的状态改为0,将前驱节点的状态改为-1