一、背景
随着java内卷越来越厉害,校招经常会问一些源码知识。例如Synchronized的实现原理,ReentrantLock的实现原理,AQS的实现原理,ConCurrentHashMap的实现原理等等。如何能够灵活的应对呢?解决方案一:背八股;解决方案二:看视频;解决方案三:看源码。
二、看源码
怎么看?
描述一下我之前看的方法哈,按着command我点,我点,我点点点,似乎看着很牛逼,从上帝视角到达了平民视角,但是点完了之后,啥玩意也不知道,只记得皮毛。今天面试的时候被问到了ReentrantLock的实现原理,我索性回答了CAS+volatile双重组合。volatile修饰了一个int类型的state,在进行更新状态的时候,使用CAS完成更新。具体公平锁和非公平锁如何实现的,脑海一片浆糊。接下来也就有了今天看ReentrantLock的源码的理由!这次思路完全不一样!我打算仿照ReentrantLock自己重新Copy一下代码。
三、Copy并非真正的Copy
如果条件允许的话,那么就左边ReentrantLock的源码,右边ReentrantLock的高仿代码,这样看起来比较方便。看到左侧代码一大坨注释,心难免有些失落。如果把八股背好的话,建议直接copy,如果没有的话,建议先把八股看完再copy。
四、高仿ReentrantLock
4.1 实现Lock接口
管他三七二十一呢?先实现Lock接口
//1.实现Lock接口
public class ReentrantLockCopy implements Lock {}
实现Lock接口的所有方法
注意,这里的实现不是真的去实现,我们先别急着实现,先别让程序报错!
4.2 仿照Sync继承AQS写 抽象 静态内部类Sync
//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
//2.编写抽象静态内部类Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
}
}
重写AQS的方法
//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
//2.编写抽象静态内部类Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
return super.tryAcquire(arg);
}
@Override
protected boolean tryRelease(int arg) {
return super.tryRelease(arg);
}
@Override
protected int tryAcquireShared(int arg) {
return super.tryAcquireShared(arg);
}
@Override
protected boolean tryReleaseShared(int arg) {
return super.tryReleaseShared(arg);
}
@Override
protected boolean isHeldExclusively() {
return super.isHeldExclusively();
}
}
@Override
public void lock() {
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
}
@Override
public Condition newCondition() {
return null;
}
}
4.3 修改tryAcquire方法名称
将方法名修改为nonfairTryAcquire,同时删除@Override注解,修改参数名称为acquires,方法修饰符为final boolean
//1.实现Lock接口
public class ReentrantLockCopy implements Lock {
//2.编写抽象静态内部类Sync
abstract static class Sync extends AbstractQueuedSynchronizer{
/**
*
* @param acquires
* @return
*/
final boolean nonfairTryAcquire(int acquires) {
return super.tryAcquire(acquires);
}
nonfairTryAcquire这个方法是尝试进行加锁的意思。尝试加锁:
第一步:我们需要获取当前的线程。
第二步:获取state的状态。
第三步:判断当前是否加锁,如果state为0表示未被加锁,如果非0则需要接下来的判断->当前的锁是否为可重入锁。
第四步:如果当前未被加锁,则使用CAS完成state状态的更新,即target是否于expect相同,如果相同则更改为update的值。同时需要设置当前加锁的线程为当前的线程。
第五步:判断当前是否加了可重入锁。即一个线程多次加锁。如何判断呢?就是判断加锁的线程是否和当前线程相同,相同的话,需要获取state的状态变量,同时将传递的参数acquires加上,作为新的state状态值。如果当前新的状态值超过了最大的可重入次数,那么不好意思,抛出错误,至于为啥可能是负数,首先我们应该明确的是int的范围为(-231-1,231-1),重入的多了,肯定会溢出。如果为非负数的话,更新state状态,返回true。
第六步:返回false。
代码如下
/**
* 非公平锁尝试加锁
*
* @param acquires
* @return
*/
final boolean nonfairTryAcquire(int acquires) {
//获取当前尝试加锁的线程
final Thread current = Thread.currentThread();
//获取当前state的状态
int c = getState();
//如果当前的状态为无锁状态,即c为0,使用cas进行加锁
if (c == 0) {
if (compareAndSetState(0, 1)) {
//设置当前锁的拥有者为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
//如果当前锁为可重入锁,即加锁的线程为当前的线程,完成state的加1操作
else if (current == getExclusiveOwnerThread()) {
int nextc = getState() + acquires;
if (nextc < 0) {
throw new Error("可重入锁加的太多了,出现溢出了");
}
setState(nextc);
return true;
}
//如果加锁失败,并且非可重入锁,则加锁失败
return false;
}
4.4 编写锁释放tryRelease方法
锁释放需要先获取当前的state的状态值,然后判断当前占据锁的线程和当前线程是否一致,如果不一致,抛出IllegalMonitorStateException异常。接下来需要判断当前的state是否为0,如果为0的话,则先释放锁。否则的话,当前是可重入锁,将state状态值更新,返回释放失败。
代码如下:
protected final boolean tryRelease(int releases) {
//获取当前state的状态
int c = getState() - releases;
//判断加锁的线程是否为当前的线程
if (!isHeldExclusively()) {
throw new IllegalMonitorStateException("当前线程不是锁的持有者");
}
//如果当前的state的状态为0的话,即可以释放锁
boolean free = false;
if (c == 0) {
free = true;
//设置当前加锁的线程为null
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
4.5 完成其他的方法重写。
/**
* 判断当前线程是否为锁的拥有者
*
* @return
*/
@Override
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
/**
* 我也不知道干啥
*
* @return
*/
final ConditionObject newCondition() {
return new ConditionObject();
}
/**
* 通过state即可获取当前锁的持有者,有人拿着就不是0,否则为0
*
* @return
*/
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final boolean isLocked() {
return getState() != 0;
}
/**
* 获取当前线程可重入锁的加锁次数
*
* @return
*/
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
/**
* 从流中恢复实例(即反序列化)。
* 它指的是将序列化的数据转换回原始对象形式的过程。
* 换句话说,它是指将序列化的数据转换为原始对象的过程。
*
* @param s 输入流
*/
private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
4.6 整体代码 自行看ReentrantLock吧!
4.7 编写非公平锁的逻辑
第一步是继承copy的Sync抽象静态内部类。
static final class NoFairSync extends Sync {}
其次编写加锁逻辑。注意这里是使用CAS完成了加锁,如果当前的state的状态值为0的话,加锁成功,否则的话,调用AQS为我们提供的acquire方法。
final void lock() {
if (compareAndSetState(0, 1)) {
//加锁成功使用CAS设置当前锁的拥有者
setExclusiveOwnerThread(Thread.currentThread());
} else {
acquire(1);
}
}
AQS中acquire的代码。(AQS这个代码等着下次分析AQS再作解读)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
最后编写tryAcquire尝试加锁的逻辑,主要还是调用我们之前copy的nonfairTryAcquire方法。
//调用抽出的公共内部类完成尝试加锁操作
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
4.8 编写公平锁的逻辑
第一步也是继承copy的Sync抽象静态内部类。
static final class FairSync extends Sync {}
其次编写lock方法。**注意:**这里公平锁直接调用了AQS提供的acquire方法。而非先使用CAS判断当前是否加锁,设置当前的线程为加锁的线程;否则使用acquire方法。
@Override
final void lock() {
acquire(1);
}
尝试加锁的过程只有先判断AQS中的双向链表构成的队列前继节点是否为空。其余都一样!这也是公平锁与非公平锁的差异!
//尝试加锁
protected final boolean tryAcquire(int acquire) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//注意这里和非公平锁的区别!如果CLH队列中存在节点,则将当前的节点入队
if (!hasQueuedPredecessors() && compareAndSetState(0, acquire)) {
setExclusiveOwnerThread(current);
return true;
}
} else if (Thread.currentThread() == getExclusiveOwnerThread()) {
int nextc = acquire + c;
if (nextc < 0) {
throw new Error("重入次数太多");
}
setState(nextc);
return true;
}
return false;
}
4.9 剩下的方法代码,仿照ReentranLock Copy即可
4.10 编写ReentrantLockCopy的构造方法
第一个构造方法:ReentrantLock默认使用非公平锁!
public ReentrantLockRewrite() {
sync = new NoFairSync();
}
第二个构造方法:如果使用公平锁,构造方法的参数为true!
public ReentrantLockRewrite(boolean flag) {
sync = flag ? new FairSync() : new NoFairSync();
}
五、结语
其实ReentrantLock的源码也没多少,大家要静下心来慢慢看就行了,用心灵感悟大师的奥秘!
六、常见的面试题
1.ReentrantLock公平锁和非公平锁的实现原理,以及区别!
2.ReentrantLock如何实现可重入锁!
3.ReentrantLock 实现可重入锁的时候为何需要判断state小于0的条件?
4.ReentrantLock 可中断锁的实现原理!
5.ReentrantLock与Synchronized的区别!