1.概述
ReentrantLock
、CountDownLatch
、Semaphore
底层都是基于AQS
实现的,其中tryAcquire
、tryRelease
、tryAcquireShared
、tryReleaseShared
即加锁以及释放锁的逻辑则由这三个子类自己实现。AQS的实现细节详见AbstractQueuedSynchronizer(AQS)中独占模式与共享模式的设计与实现。
2.ReentrantLock
2.1 ReentrantLock
中加锁逻辑的实现
ReentrantLock
中拥有Sync
类型的内部类,其中Sync
继承AQS
,采用独占模式下的AQS
,Sync
的子类包括NonfairSync
(非公平)与FairSync
(公平)两种模式,所有对AQS
的操作便通过Sync
进行。在初始化的时候会实例化一个Sync
对象,默认实现为非公平模式。
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
同时也可以指定Sync
的实现为公平模式or非公平模式
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
2.1.1 ReentrantLock
非公平模式下的加锁实现
非公平模式下获取锁的逻辑,线程第一次进入的时候会利用cas操作更新state
状态为1,如果更新成功,则设置当前线程为owner;当cas操作失败(包括两种情况1.重入锁逻辑:已经获取锁的线程再次调用lock
方法;2.当前线程持有锁,其他线程调用lock
方法,即持有锁的线程和调用lock
方法的线程不是同一个),则进入到acquire
方法中,acquire
会优先调用子类的tryAcquire
方法,具体获取锁的实现就是在该方法中。
/**
* Sync object for non-fair locks
*/
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() {
/*第一次获取锁时,设置state为1*/
if (compareAndSetState(0, 1))
/*设置当前线程为owner*/
setExclusiveOwnerThread(Thread.currentThread());
else
/*抢夺锁以及重入锁逻辑*/
acquire(1);
}
/*获取锁的具体实现*/
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
tryAcquire
中调用nonfairTryAcquire
方法,采用cas操作更新state
的值,成功后就设置当前线程为owner,代表获取锁成功;如果当前线程等于owner,就累加state
值(重入锁的实现逻辑),多次调用lock
方法加锁,解锁时也要调用同样次数的unlock
方法,直到state
值减为0。
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
final boolean nonfairTryAcquire(int acquires) {
/*获取当前线程*/
final Thread current = Thread.currentThread();
/*获取state值*/
int c = getState();
if (c == 0) {
/*cas操作更新state状态*/
if (compareAndSetState(0, acquires)) {
/*更新成功后设置当前线程为owner*/
setExclusiveOwnerThread(current);
return true;
}
}
/*如果当前线程是onwer(重入锁逻辑)*/
else if (current == getExclusiveOwnerThread()) {
/*获取state的值,并且加上本次的acquires再更新state*/
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
/*获取锁失败*/
return false;
}
2.1.2 ReentrantLock
公平模式下的加锁实现
公平模式下的加锁实现与非公平的比较在于多了hasQueuedPredecessors
方法的判断,该方法主要是判断阻塞队列的队首是否含有元素以及如果队首有元素是否与当前线程相等,目的其实就是检测有没有排在当前线程之前的线程需要出队,严格保证先进先出。tryAcquire
除了在阻塞线程被唤醒的时候会被调用外,外部代码调用lock
方法的时候也会间接调用到,当外部代码调用lock
时,就会通过hasQueuedPredecessors
方法校验阻塞队列中是否还有等待中的线程,如果有,就不会让该线程获取锁。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*hasQueuedPredecessors用于判断AQS的阻塞队列里是否有等待中的线程*/
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;
}
}
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());
}
2.2 ReentrantLock
释放锁逻辑
公平模式与非公平模式下释放锁的实现都是相同的,tryRelease
方法在Sync
类中实现。释放锁的过程就是减少state
的值直到0(因此在重入锁的情况下,调用unlock
的次数要与lock
的次数相同),同时将owner置空。
protected final boolean tryRelease(int releases) {
/*获取state值并减少对应次数*/
int c = getState() - releases;
/*判断当前线程是否为owner*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
/*state减为0后,设置owner为null*/
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2.3 ReentrantLock
中lockInterruptibly
响应中断原理
当线程调用lock
方法后并处于阻塞状态下,该线程是无法响应中断的,只有获取到锁之后才能响应中断,但是使用lockInterruptibly
就能在阻塞状态下响应中断。lockInterruptibly
方法调用acquireInterruptibly
(该方法在AQS
中实现)
public final void acquireInterruptibly(int arg)
throws InterruptedException {
/*判断是否被中断过,如果被中断过则直接抛出异常*/
if (Thread.interrupted())
throw new InterruptedException();
/*获取锁失败则调用doAcquireInterruptibly方法*/
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
重点关注doAcquireInterruptibly
方法,该方法的实现与acquireQueued
非常相似(acquireQueued
实现可以参考AQS),区别在于阻塞状态下的线程因为中断被唤醒时,parkAndCheckInterrupt
返回true,就会抛出InterruptedException
异常。
/**
* Acquires in exclusive interruptible mode.
* @param arg the acquire argument
*/
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;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
/*线程因为中断被唤醒后直接抛出异常*/
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3.CountDownLatch
CountDownLatch
同样也拥有一个Sync
类型的内部类,其中Sync
继承AQS
,使用的是共享模式下的AQS
,Sync
的子类只有一个,在初始化的时候实例化。Sync
的实现如下,包括构造函数(给state
赋初始化值),获取锁逻辑(判断state
值是否为0,为0则返回1否则返回-1),释放锁逻辑(state
值减少1)。
/**
* Synchronization control For CountDownLatch.
* Uses AQS state to represent count.
*/
private static final class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = 4982264981922014374L;
/*初始化的时候state的值*/
Sync(int count) {
setState(count);
}
int getCount() {
return getState();
}
/*获取锁逻辑*/
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/*释放锁逻辑*/
protected boolean tryReleaseShared(int releases) {
// Decrement count; signal when transition to zero
for (;;) {
/*获取state状态*/
int c = getState();
if (c == 0)
return false;
/*state值减1并使用cas更新*/
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
}
2.1.1 CountDownLatch
初始化构造
CountDownLatch
初始化的时候会根据传入的值设置state
的值,当调用countDown
方法的时候会把该值减少1,直到减为0的时候,阻塞中的线程就能继续执行。
public CountDownLatch(int count) {
/*校验count合法性*/
if (count < 0) throw new IllegalArgumentException("count < 0");
/*设置state初始化值*/
this.sync = new Sync(count);
}
2.1.2 await
方法实现原理
调用await
方法时会调用acquireSharedInterruptibly
,该方法会首先判断当前线程是否有中断标记,如果有则直接抛出异常。接着会调用tryAcquireShared
方法获取锁,该方法通过state
是否等于0决定返回1还是-1,因为在构造函数中,会初始化state
的值为传递进去的数字,所以在没有调用countDown
方法的前提下(同时初始化传递的值为非0),tryAcquireShared
获取锁是一定会失败的,因此返回-1,接着进入AQS
提供的doAcquireSharedInterruptibly
方法中。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
/*如果有中断标记则直接抛出异常*/
if (Thread.interrupted())
throw new InterruptedException();
/*获取锁失败则进入阻塞队列*/
if (tryAcquireShared(arg) < 0)
/*AQS方法,让当前线程进入阻塞队列并阻塞*/
doAcquireSharedInterruptibly(arg);
}
doAcquireSharedInterruptibly
在该场景下主要完成两件事情,1.将当前线程加入到阻塞队列中;2.阻塞当前线程。doAcquireSharedInterruptibly
与doAcquireShared
(doAcquireShared实现参考)的实现很相似,区别在于doAcquireSharedInterruptibly
如果因为中断被唤醒,就会直接抛出异常,该方法是能响应中断的。
/**
* Acquires in shared interruptible mode.
* @param arg the acquire argument
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
/*进入阻塞队列*/
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
/*如果是因为中断被唤醒线程则直接抛出异常*/
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
因此当前线程调用await
方法的时候,如果state
不为0时就会被阻塞,等待其他线程调用countDown
直到state
减少到0才会继续执行下去,同时,如果阻塞中的线程被调用中断方法,则会直接抛出InterruptedException
异常。
2.1.3 countDown
方法实现原理
countDown
方法最终会调用CountDownLatch
实现的tryReleaseShared
释放锁方法,该方法会将state
中的值减少1,并返回state
是否等于0的信息。当调用countDown
方法直到state
减少到0的时候,tryReleaseShared
就会返回true,通过调用AQS
中的doReleaseShared
方法,唤醒阻塞队列中因为调用await
所阻塞的线程。
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
/*获取锁成功则唤醒等待队列中的线程*/
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
3.Semaphore
3.1 Semaphore
的构造方法
Semaphore
同样拥有Sync
类型的内部类,其中Sync
继承AQS
,采用共享模式下的AQS
,Sync
的子类与ReentrantLock
类似,包括NonfairSync
(非公平)与FairSync
(公平)两种模式,默认实现为非公平模式。
public Semaphore(int permits) {
sync = new NonfairSync(permits);
}
同时也能指定使用哪一种模式
public Semaphore(int permits, boolean fair) {
sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}
Semaphore
初始化的时候,会传递permits
,该值会被设置为state
的值。
3.2 acquire
方法实现原理
acquire
会调用acquireSharedInterruptibly
方法,tryAcquireShared
方法(获取锁逻辑)由Semaphore
自己实现。如果获取锁失败,则进入阻塞队列。
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
/*发现中断标记则直接抛出异常*/
if (Thread.interrupted())
throw new InterruptedException();
/*获取锁失败则进入阻塞队列*/
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared
有两种不同的实现,先说在非公平模式下的实现,直接减去acquires
的大小并更新state
final int nonfairTryAcquireShared(int acquires) {
for (;;) {
/*获取state的值*/
int available = getState();
/*较少acquires的值并更新state*/
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
公平模式下获取锁的逻辑相对于非公平模式下的多了hasQueuedPredecessors
函数判断出队的元素是否位于队首,如果是外部代码调用的,则判断阻塞队列中是否还有未出队的元素,此处逻辑与ReentrantLock
的公平锁实现逻辑相似。
protected int tryAcquireShared(int acquires) {
for (;;) {
if (hasQueuedPredecessors())
return -1;
int available = getState();
int remaining = available - acquires;
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
3.3 release
方法实现原理
release
方法会调用releaseShared
,其中tryReleaseShared
(释放锁)成功后就会通过AQS
提供的doReleaseShared
方法唤醒阻塞队列中的线程。
public final boolean releaseShared(int arg) {
/*成功释放则唤醒阻塞队列中的线程*/
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared
的逻辑非常简单,就是增加对应的releases
值并更新state
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
/*增加state的值并更新*/
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
4.总结
对于ReentrantLock
、CountDownLatch
、Semaphore
实现加锁解锁逻辑最重要的变量是AQS
中的state
变量。
-
ReentrantLock
加锁流程就是将state
增加1,解锁便是减1,所以调用多次lock
方法就要调用对应次数的unlock
方法才能解锁。 -
CountDownLatch
初始化的时候设置state
的值,当其他线程调用countDown
的时候就减少state
的值,直到state
减少为0的时候,因为调用await
方法而在阻塞队列中等待的线程,就会在最后一次调用countDown
的时候被唤醒。 -
Semaphore
同样也是初始化的时候设置state
的值,调用acquire
成功获取锁的时候就会减少state
的值,如果state
减少到0则无法再成功获取锁,线程就会进入到阻塞队列中;调用release
释放锁的时候会增加state
的值。