JUC之ReentrantLock

一、背景

随着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的区别!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值