ReentrantLock学习
基于独占模式的ReentrantLock(JDK1.8)
顶层接口
public interface Lock {
// 加锁
void lock();
// 可打断的锁,当在获取不到锁等待时,可以被打断,直接返回
// synchronized是不支持的,不会被打断
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
// 在time时间内尝试加锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
// 多个线程可以在不同的条件变量里等待
Condition newCondition();
}
关键变量
private final Sync sync;
这个变量是用AQS同步器来实现的。提供了两种实现,一种是公平锁,一种是非公平锁。默认构造器是用了非公平锁。这里需要深刻理解公平和非公平的含义。
- 公平锁:我们现实世界里,遵循先来后到的原则才叫公平。每个线程来了,要先到后面去排队,不能直接去抢锁,这样每个线程都有可能获得锁。
- 非公平锁: 新来的线程先不去排队,直接和队列里的线程去抢锁,这样自然不公平的。但是性能好,新来的线程有一定概率是立刻获得锁的,但是可能造成队列里的线程很长时间都得不到锁。
原理直接源码
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)) // 线程来了,直接进行CAS去修改状态变量的值
setExclusiveOwnerThread(Thread.currentThread());
else // 修改失败,才会去到AQS的加锁流程,尝试加锁 -》 入队
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() { // 加锁操作直接调用AQS的加锁方法,尝试加锁的方法在下面
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) { // 未加锁状态
if (!hasQueuedPredecessors() && // 队列里面没有线程去等待获取锁直接CAS设置锁,如果有的话就返回false了,说明AQS尝试加锁失败了,开始正式入队并加锁。
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; // AQS队列里面有线程,而且已经上锁了并且锁的所有者不是当前线程
}
}
关键方法
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout); // 这种逻辑运算的函数调用简直NB啊,tryAcquire失败,就正式的加锁
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
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 true;
}
nanosTimeout = deadline - System.nanoTime();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold) // 还有剩余时间, 直接睡眠,等待锁释放的时候或者是时间到了就唤醒
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
总结
- 宏观上来说,ReentrantLock在Java语言层面实现了一把锁,我们作为Java开发者, 可以去扩展,对我们更友好。
- 场景上来说,ReetrantLock支持公平锁和非公平锁,支持可打断的锁,支持一定时间内的加锁操作
- 原理上来说,ReentrantLock基于AQS, 使用状态变量waitStatus + CAS + 队列的来管理线程。
个人的思考
今天分析了ReentrantLock,发现同步器AQS才是重点,他为我们提供了一种抽象的框架,上层应用已经为我们写好了加锁的方法,唯一不同的是tryAquire方法的实现,这就是模板方法的应用,把不容易变的抽象到父类,形成一个模板,子类重写模板中的容易变的方法就好了。