ReentranLock内部机制

作者: 慧明小和尚下山去化斋 | 来源:发表于2017-02-14 18:19 被阅读0次

前言

JDK给我们提供了可重入锁ReentrantLock,下面我就对它的可重入这一机制进行描述。

开始

使用ReentrantLock,如下就是一个很简单的例子。

static final ReentrantLock lock = new ReentrantLock();

static class Task implements Runnable {
        @Override
        public void run() {
            lock.lock();
            System.out.println(Thread.currentThread());
            lock.unlock();
        }
}

开始学习并发包的时候我一直很奇怪它为什么叫重入锁,原来是可以执行lock()方法多次,要释放该锁,lock()方法执行了几次就要执行unlock()方法几次。
但是这又是怎么实现的呢?

细究

lock()方法中,我们可以看到,实际上执行上锁操作的是这个sync对象。

lock()方法 Sync类
Sync是一个抽象类,它的lock()方法是一个抽象方法,交给子类去实现,Sync有两种实现类,一个是FairSync,另一个是NonfairSync,这就是我们经常说的公平锁和非公平锁。为了更好地去比较这两种实现方式,先分析公平锁。

FairSync

FairSync
FairSync直接调用了acquire(1)方法,这实际上就是要对AQS类中定义的state属性进行+1操作,而且根据继承关系,FairSyncNonFairSync都算是AQS
acquire方法(AQS类)
acquire(int)方法里面,首先会尝试去获取锁,也就是执行tryAcquire(int)方法,根据短路效应,如果获取锁成功,tryAcquire(int)将会返回true,取反就是false,那么if语句里边的acquireQueued(Node)方法将不会执行,自此,lock操作语句执行成功,拿到锁的线程将会越过该lock语句走到临界区。
tryAcquire(int)方法在FairSync里边已经重写了,如下。

先尝试tryAcquire

tryAcquire(int)方法(FairSync类)
在该方法中,首先应该拿到当前的state,如果state0,也就是锁没有被任何线程占用,首先判断在等待队列(也就是CLH队列)中没有其他线程在当前线程前面等待(!hasQueuedPredecessors())没有的话就用CAS将state设置成acquire,其实就是1。最后,把占有该锁的线程标识为当前的线程(setExclusiveOwnerThread(current);),又是一个短路操作,设置的还是很巧妙。
然后,else if语句表示的意思是,如果有线程已经占有了该锁,那么判断当前尝试的线程是不是拥有该锁的线程,如果是的话,state+1
最后,上述两种情况都没满足,获取失败,返回false
再附上相关的两个方法的代码 hasQueuedPredecessors() (AQS类)
setExclusiveOwnerThread (AQS的父类)
上面就是涉及到的方法,排队的原理很简单,在CLH队头的线程就是有资格竞争锁的线程了,因为它能够执行接下来置换state属性的CAS操作。

tryAcquire失败了

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

如若尝试失败了,那就把该线程加入CLH等待队列中了。也就是执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法。

我们先来看addWaiter(Node)方法。

enq和addWaiter
这里实际上就是向CLH队尾加入一个等待线程,因为是在多线程的情况下,需要用CAS置换成新的tail
enq(Node)方法中,使用了懒加载的模式,也就是说,如果tailnull,就新建一个Node,然后CAS置换成新的Head之后,把head给tail,初始化就完成了。
初始化完成之后,就到了else分支中,也同样是CAS置换tail指针,最后用返回原先的节点跳出了死循环。但在addWaiter方法中,返回的是新加入的节点。
我们已经入队了,公平锁嘛,接下来不就是排队咯。 acquireQueued
CLH队列使用了自旋锁的一些机制,所以代码中会有一个死循环,就像刚才说的,在队头的线程有资格去竞争(contend)锁,也就是p == head && tryAcquire(arg)语句了,竞争成功了,前任走了,自然就有if大括号里的语句了。

NonfairSync

NonfairSync

与公平锁不同的是,非公平锁会事先去抢占锁,野蛮的进行插队,也就是率先执行的CAS置换state属性的操作,抢占没成功,这下老实了,才开始用公平锁一样的方法去获得锁。

NonfairSync的tryAcquire具体实现

但是非公平锁可不是那么老实的,它自己的尝试还是跟公平锁有区别的,也就是上图我划出的地方,他没有判断当前线程是否位于队头。这就是它们俩的区别了。

释放锁

tryRelease (Sync类)

刚刚是+1,释放锁当然就是-1了,当然还要注意操作的线程是不是获取了该锁的线程,如果减到了0,那这个锁就没主人了。

结尾

ReentranLock通过对state属性进行加减操作实现可重入的功能,对获取不到锁的线程进行了自旋操作。当然,我只描述我看的有思路的,这个类还有很多地方值得我们学习。

相关文章

  • ReentranLock内部机制

    前言 JDK给我们提供了可重入锁ReentrantLock,下面我就对它的可重入这一机制进行描述。 开始 使用Re...

  • Reentranlock

    Java多线程(九)之ReentrantLock与Condition

  • 重入锁的源码解析

    今天来分析一下重入锁的源码 ReentranLock定义 重入锁ReentranLock是一种支持重进入的锁,表示...

  • Window内部机制

    前言 Window表示一个窗口, Android中所有视图都是通过Window来呈现的, 例如Activity, ...

  • RemoteViews内部机制

    1.RemoteViews 支持的view 类型,不支持他们的子类及其他类型, Layout : FrameLay...

  • 字典内部机制

    这章我们将深入Forth的引擎内部,了解其底层机制 这里可能重复介绍些我们以前已经接触过的机制,为了从整体上审视F...

  • CyclicBarrier内部机制

    前言 如果我们希望所有的线程都到达同一个地方才能继续往下执行,那么CyclicBarrier就是一个不错的选择。 ...

  • ThreadLocal内部机制

    前言 下面是一个使用ThreadLocal的小demo 为每个线程都创建一个ThreadLocal,最后输出每个线...

  • CountDownLatch内部机制

    前言 在开始之前,就开始一个小实验,在这个实验里面,我希望main线程等待CountDownLatch减少它的计数...

  • ThreadPoolExecutor内部机制

    前言 众所周知,JDK为我们提供了一系列线程池类,ThreadPoolExecutor就是一个很典型的实现,以下对...

网友评论

    本文标题:ReentranLock内部机制

    本文链接:https://www.haomeiwen.com/subject/jjwxwttx.html