前言:
之前说过,JDK有两种锁的方式,一种是Synchronized,一种就是CAS
基于CAS实现的Unsafe我们无法直接使用,我们一般就是使用ReentrantLock来实现锁的功能
那么本文我们就来看下ReentrantLock是如何实现CAS锁的
建议读者也看一下关于AbstractQueuedSynchronizer的源码解析,AQS作为一个基础类,ReentrantLock会用到里面的很多方法
可以参考下笔者的另一篇博客:JDK源码解析之AbstractQueuedSynchronizer
1.ReentrantLock结构分析
public class ReentrantLock implements Lock, java.io.Serializable {
// 只有这么一个成员变量
private final Sync sync;
// 两种构造方法
public ReentrantLock() {
// 默认是非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
// 可以让用户自主选择公平锁或非公平锁
sync = fair ? new FairSync() : new NonfairSync();
}
1)有关于ReentrantLock.Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
private static final long serialVersionUID = -5179523762034025860L;
abstract void lock();
...
}
2)ReentrantLock.FairSync公平锁
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
...
}
}
3)ReentrantLock.NonFairSync非公平锁
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
总结:通过上面的结构分析可知
基础的实现类就是Sync,而Sync继承了AQS
非公平锁NonFairSync继承了Sync
公平锁FairSync也继承了Sync
4)ReentrantLock的基本使用
就是我们之前看过的ArrayBlockingQueue.offer(E e)
// ArrayBlockingQueue.offer(E e)
public boolean offer(E e) {
checkNotNull(e);
final ReentrantLock lock = this.lock;
// 获取锁
lock.lock();
try {
if (count == items.length)
return false;
else {
enqueue(e);
return true;
}
} finally {
// 释放锁
lock.unlock();
}
}
由上例可知:ReentrantLock的使用还是比较简单的,lock()方法就是用来获取锁;unlock()就是用来释放锁。
下面来着重分析一下这两个方法(我们来分析默认的非公平锁)
2.ReentrantLock.lock()获取锁(非公平锁NonFairSync)
// ReentrantLock.lock()
public void lock() {
sync.lock();
}
// NonFairSync.lock()
final void lock() {
// 使用cas设置state为1
if (compareAndSetState(0, 1))
// 如果设置成功则置当前线程为独占线程
setExclusiveOwnerThread(Thread.currentThread());
else
// 如果设置state失败,说明已经被其他线程设置成功了,
// 则进入AQS.acquire方法
// 关于acquire的具体细节可以参考笔者关于AQS分析的博客
acquire(1);
}
// AbstractQueuedSynchronizer.compareAndSetState()
protected final boolean compareAndSetState(int expect, int update) {
// See below for intrinsics setup to support this
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
总结:NonFairSync.lock()方法还是比较简单的,直接就是使用CAS来设置AQS.state=1,成功则说明当前线程已经竞争到锁,可以继续下面的业务处理;如果失败,则说明当前线程竞争锁失败,需要进入队列等待锁被释放继续后面的竞争。
3.ReentrantLock.unlock()释放锁(非公平锁NonFairSync)
// ReentrantLock.unlock()
public void unlock() {
sync.release(1);
}
// NonFairSync.release()
// 直接就进入了AQS.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;
}
总结:ReentrantLock.unlock()直接就调用了AQS.release()
具体不再分析,读者可参考JDK源码解析之AbstractQueuedSynchronizer
4.ReentrantLock的其他加锁方式
1)ReentrantLock.tryLock()尝试锁,如果失败则直接返回,不再等待
// ReentrantLock.tryLock()
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
// Sync.nonfairTryAcquire()
final boolean nonfairTryAcquire(int acquires) {
// 1.获取当前线程及状态
final Thread current = Thread.currentThread();
int c = getState();
// state=0则意味着没有线程获取到锁,使用CAS将state设置为1,
// 返回true代表当前线程获取到锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 2.如果发现c!=0但是持有锁的线程是当前线程的话,
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
// state累加,如果<0了,说明已经溢出了,这个时候就只能报错
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 大于0的情况是怎么回事呢?
// 当前线程多次获取这个锁,则每获取一次就加1
setState(nextc);
return true;
}
// 如果c!=0而且持有锁的线程也不是当前线程,则直接返回false
return false;
}
2)ReentrantLock.tryLock(long time,TimeUnit unit)尝试加锁,如果在规定时间内还是失败则直接返回,不再等待
// ReentrantLock.tryLock(long time,TimeUnit unit)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
// AbstractQueuedSynchronizer.tryAcquireNanos()
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
// 1.如果线程已经被设置中断状态,则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
// 2.tryAcquire调用的是Sync.tryAcquire()
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
// Sync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
// 直接调用了nonfairTryAcquire()方法,在1)中我们已经分析了
return nonfairTryAcquire(acquires);
}
// AbstractQueuedSynchronizer.doAcquireNanos()
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 1.设置期限值
final long deadline = System.nanoTime() + nanosTimeout;
// 2.创建新节点,并设置到队列尾部
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
// 3.获取前置节点,如果前置节点是head,则尝试获取锁操作
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 4.如果当前时间已经超过期限值,则直接返回false
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
// 5.没有超过期限值,也没有获取到锁的线程则进入休息状态
// 休息时间为nanosTimeout
if (shouldParkAfterFailedAcquire(p, node) &&
// 如果剩余时间超过1秒钟的话,就睡
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
// 6.接收中断响应,发现线程被中断,则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
3)ReentrantLock.lockInterruptibly()可中断锁,在尝试加锁的过程中,如果线程被中断,则直接返回
// ReentrantLock.lockInterruptibly()
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
// AbstractQueuedSynchronizer.acquireInterruptibly()
public final void acquireInterruptibly(int arg)
throws InterruptedException {
// 1.接收中断响应,发现线程被中断,则直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// Sync.tryAcquire()
protected final boolean tryAcquire(int acquires) {
// 直接调用了nonfairTryAcquire()方法,在1)中我们已经分析了
return nonfairTryAcquire(acquires);
}
// AbstractQueuedSynchronizer.doAcquireInterruptibly()
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
// 1.创建新节点,并添加到队列尾部
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
// 2.获取前置节点,如果前置节点是head,则尝试获取锁操作
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
// 3.也没有获取到锁的线程则进入休息状态
// parkAndCheckInterrupt()方法返回线程的中断状态,如果发现线程已经被中断,则直接抛出异常
// 否则线程就进入休息状态,等待唤醒
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
// AbstractQueuedSynchronizer.parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
总结:以上三种加锁方式的区别
1)tryLock() 直接使用CAS设置state=1,成功就成功,不成功就拉倒
2)tryLock(long time,TimeUnit unit) 先去竞争锁,不成功的话,则进入队列等待,休息规定时间后,醒来再去获取锁,不成功就拉倒
3)lockInterruptibly() 先查询线程中断状态,及时响应中断;然后去竞争锁,失败的话,则进入队列等待,休息的同时检测线程中断状态
5.公平锁(FairSync)的处理方式
上述看的都是关于非公平锁的分析,我们来简单看下公平锁是如何竞争资源的
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
// lock也是调用AQS.lock()方法
// 这个不再分析
final void lock() {
acquire(1);
}
// AQS.lock方法会调用FairSync.tryAcquire()方法
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 1.state=0代表没有线程获取锁
if (c == 0) {
// 判断当前线程节点之前是否还有其他节点在等待
// 如果没有才去尝试获取锁
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
// 这里的处理与NonFairSync一样
// 也是判断获取锁的线程是否当前线程,如果是当前线程,则将state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
// AbstractQueuedSynchronizer.hasQueuedPredecessors()
public final boolean hasQueuedPredecessors() {
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());
}
非公平锁和公平锁最主要的区别就是:如果state=0时(也就是没有其他线程获取锁),公平锁需要先判断当前线程节点之前是否还有其他节点,如果有,则不去竞争锁;非公平锁则不关心这个,一旦发现state=0,则直接使用CAS获取锁