

























面试中遇到“ ReentrantLock 实现原理?”这个问题,我们应该怎么回答?是否直接就开始介绍 AQS, CLH 队列,这些高大上的词语。这样的回答会给面试官两个不好的印象:
因此在这里提供一个回答问题的思路:按照功能介绍,需求分析,需求实现,源码实现,实现总结,共5步进行回答。这样有什么好处呢?
ReentrantLock 是一个可重入互斥锁,是 Lock 接口的实现类,是控制线程之间同步访问共享资源的一种方式。
ReentrantLock 用来保证线程安全。线程安全是在多线程并发访问共享资源时,通过同步机制协调各线程执行,来确保得到正确结果。
在需要控制线程之间同步访问共享资源时使用 Lock,但是只有以上需求时,我们完全可以使用 synchronized,没有必要使用 ReentrantLock。因此,当还有下面需求时才考虑使用 ReentrantLock :
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}}
Lock l = ...;
try {
l.lockInterruptibly();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}}
} catch (InterruptedException e){
// 中断后处理
}
Lock l = new ReentransLock(true);
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await();
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await();
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
我们根据 ReentrantLock 定义的方法,将这些方法具有的特性进行分析,并拆分为每个小的需求点,通过实现小的需求点,并组合,最终实现 ReentrantLock 的功能。这样做可以降低实现难度,也可以简化实现原理。
根据需求分析,探讨实现每个需求的原理。
公平锁
使用线程安全队列按照线程执行 lock 方法的先后顺序添加进队列。
非公平锁
随机让阻塞线程获取锁。
无阻塞获取锁
使用 int state 变量作为锁状态的标志,0表示没有线程占用锁,1表示有线程占用锁。获取锁的伪码如下:
private int state;
public boolean tryLock(){
if state == 0
state = 1;
return true;
else
return false;
}
这样会有两个问题:
state==0 and state=1是非原子操作(执行两步)会有线程安全问题。解决办法:
unsafe.compareAndSwapInt(this, stateOffset, 0, 1),CAS 保证原子性。private volatile int state,volatile 保证可见性。所以最终伪码:
private volatile int state;
public boolean tryLock(){
return unsafe.compareAndSwapInt(this, stateOffset, 0, 1);
}
阻塞
有5种方法实现阻塞:
while(time>0)这种方法占用 CPU 资源,不推荐。Object.wait(timeout)需配合 synchronized 使用,不推荐。Thread.join实现较为复杂,不推荐。Thread.sleep实现较为复杂,不推荐。UNSAFE.park(false, 0) LockSupport.park()推荐。可重入
使用 Thread exclusiveOwnerThread变量存当前占用锁的线程,当线程再次获取锁时,将 state++。伪码如下:
private volatile int state;
private transient Thread exclusiveOwnerThread;
if (current == exclusiveOwnerThread) {
state ++;
return true;
}
阻塞指定时间
在实现“互斥---一直阻塞”时,已经列举5种方法,并且推荐UNSAFE.park(false, 0) LockSupport.park()。所以为保持实现方法统一,推荐使用:
UNSAFE.park(false, nanos)LockSupport.parkNanos(blocker, nanos)LockSupport.parkUntil(blocker, deadline)解锁
只有持有锁的线程才能释放锁,当释放锁时state--,伪码如下:
private volatile int state;
public void unlock() {
if (current == exclusiveOwnerThread) {
state --;
}
}
解除阻塞
解除阻塞和实现阻塞方法对应,方法如下:
UNSAFE.unpark(thread)LockSupport.unpark(thread)响应中断
线程中断时调用 Thread.interrupt() ,LockSupport.park() 会响应中断,解除阻塞。
newCondition
先不考虑,比较复杂,需单独写一篇文章。
通过阅读源码,分析上面列出的需求,来分析源码是怎么实现需求的。
公平锁
CLH,线程安全的双向链表。
非公平锁
非公平锁执行 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))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
无阻塞获取锁
CAS方式获取锁,unsafe.compareAndSwapInt(this, stateOffset, expect, update)
阻塞
LockSupport.park()
可重入
获取锁的持有线程,与当前线程比较,判断是否相等,相等即可获取锁,并且修改 state。state=0表示没有线程获取锁;state=num表示线程获取锁的次数为num,释放锁时需要释放num次。
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;
}
阻塞指定时间
LockSupport.parkNanos(this, nanosTimeout)
解锁
解锁需要做如下步骤:
state -= releases;setExclusiveOwnerThread(null)。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;
}
解除阻塞
按照 CHL 队列顺序,通知下一个线程解除阻塞 LockSupport.unpark(s.thread);。
响应中断
源码实现与需求实现一致。线程中断时调用 Thread.interrupt() ,LockSupport.park() 会响应中断,解除阻塞。
newCondition
先不考虑,比较复杂,需单独写一篇文章。
本文按照,功能介绍,需求分析,需求实现,源码实现顺序讲解,介绍了 ReentrantLock 的使用、功能、实现。本文写了很多内容,如果可以用一句话介绍实现原理:
ReentrantLock 是使用链表、park、CAS、volatile 等方法实现的一个互斥且可重入的锁。
建议面试时遇到xxx原理一类的问题按照使用-原理来回答问题,这样我们可以给面试官留下不仅会使用而且还知道原理的印象。
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。