ReentrantLock原理_杨江才的博客-程序员宅基地

技术标签: java lock() 实现原理  

    先来简单通俗的描述一下  ReentrantLock     和 ReentrantReadWriteLock

   其实原理非常类似,都是通过判断   ReentrantLock  里面 有个state  ,

   ReentrantReadWriteLock  可以看成有两个 state ,一个是read_state  ,一个是write_state  

   这些东西都是通过这些state 的值进行CAS 操作加一,如果操作成功,则获取执行权限,否则放入阻塞队列呆着,并设置中断    标识,让线程停止运行

  当前获取的执行权限的 线程 执行unlock(),后,会将 state 进行CAS减一,如果state 变成0,则唤醒阻塞列队头部的线程,

 不管是公平锁还是非公平锁,都是唤醒挨着队列头的线程。公平和非公平的区别在于新进来的线程刚开始执行lock(),或者    trylock();的时候是否允许其执行CAS加一操作。

 

读写锁也是一样,只不过维护了两个state而已,哈哈哈,万变不离其宗。接下来详细介绍追涨源码

Lock lock =new ReentrantLock();
        lock.lock();
        lock.lock();

        lock.unlock();

     我们先来debug 看看这个对象里面有那些成员,

    只有一个

 private final Sync sync;

它是一个抽象内部类,sync 有两个实现,一个是公平锁,一个非公平锁

abstract static class Sync extends AbstractQueuedSynchronizer{}

他没有定义任何属性,他继承 AbstractQueuedSynchronizer

private transient Thread exclusiveOwnerThread; 哪个线程持有当前的锁

private volatile int state;     

volatile Node prev; 上一个

volatile Node next;下一个

private transient volatile Node head; 头节点

private transient volatile Node tail; 尾节点

很明显,和名字一样,抽象队列同步器

是一个双向队列,

 

Sync是一个抽象内部类,sync 有两个实现,一个是公平锁,一个非公平锁

static final class NonfairSync extends Sync

static final class FairSync extends Sync 

 

lock.lock(); 的时候就是将这个state 的值通过CAS原子性的增加1,多次调用则继续加一,则就是可重入锁。

这个这段开始这个字段为0 ,所有的线程都尝试对其修改(也就是我们说的加锁,其实本质就是修改其值),通过CAS算法修改其值,了解CAS算法的人都知道,一旦这个值被人修改成,其他人如果还拿原来的值当作预定值都无修改成功。

第一个线程修改成功后成功执行,其他那些没有成功的线程放入队列中排队,但是他们依然会不断的尝试获取锁。

compareAndSetState(0, 1),其他那些没有获得锁的线程会不断的调这个方法,自旋 for(;;)的调用这个方法,除非 占有当前锁的线程,也就是队列最前面的线程,也是exclusiveOwnerThread引用的线程,释放锁,所谓释放锁就是对这个值进行减一,当state值减到0的时候,另外那些线程的compareAndSetState(0, 1)才有可能成功。这里涉及到公平锁和非公平锁的概念。如果是非公平锁,就是我们上面讲的,所有人都可以试图执行compareAndSetState(0, 1)方法,不分队列的前面还是后面,如果是公平锁,就会进行判断,当前这个节点是否是队列的头,就是它的prev是否存在,如果前面还有人,则不能执行compareAndSetState(0, 1),这就叫公平,先到的人有优先权。

还是对着代码讲有说服力,我们来看看源码,从方法名看就知道是非公平锁。

       final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {  //等于0,大家马上看见了,所有人都试图去修改其值,并且设置自己为当前锁的获得者。
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {

     //如果不等于0,并且自己就是当前锁的拥有者,则继续加锁,继续对这  个这段加1,就是重入锁概念。
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

  

我们再看看公平锁的源码:

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    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;
        }

这逻辑和上面的没多大区别,就是去获取锁之前加了!hasQueuedPredecessors()判断当前是否是头节点,或者并且是当前锁的拥有者

 

Lock lock =new ReentrantLock(true); 这样申明的是公平锁,

Lock lock =new ReentrantLock(); 这样声明的是非公平锁,我们看看这两种声明有啥不同。

 public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

这样可以看见声明的时候可以生成两种不同的实现,

 /**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

这是公平锁的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);
        }

这是非公平锁的lock();

写的非常清楚,非公平锁,一进来就调用compareAndSetState(0, 1)去获得锁。

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

这个方法显示,如果获得失败,则添加到等待队列中,

addWaiter(Node.EXCLUSIVE), arg)

这个方法往队列的尾部添加一个节点。

 final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {  //自旋锁就是死循环
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }

                 // 检测中断标志位,自旋失败后进入重量级锁,阻塞状态
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

自旋获取锁,

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    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;
        }
    }

 

我们来看看 获取失败 ,要做什么

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

 

selfInterrupt();

 static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

public void interrupt() {
        if (this != Thread.currentThread())
            checkAccess();

        synchronized (blockerLock) {
            Interruptible b = blocker;
            if (b != null) {
                interrupt0();           // Just to set the interrupt flag
                b.interrupt(this);
                return;
            }
        }
        interrupt0();
    }

这方法就是设置一个中断标识,完了。这个线程就休息了。

接下来带头大哥执行完了,state 变回了0,要唤醒哪些线程呢,我们来看看源码,

public void unlock() {
        sync.release(1);
    }

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
 

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;
        }

将state 减一,然后判断当前线程是否是获得锁的线程,设置获得锁的线程为null,完事。

 

public final boolean release(int arg) {
        if (state ==0) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

 private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }

设置一下当前节点的状态并且唤醒下一个节点线程

 

 

 

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_36666151/article/details/100005119

智能推荐

手淘抓包、 x-sign的签名算法和api接口_frida x-sign_逆向女屌丝的博客-程序员宅基地

第一次写博客,写得不好大家别见怪。因在一个技术群里,大家突然对某宝感兴趣起来,都特别好奇淘宝接口的签名算法,因此我就在这样的情况下跟着一起研究起来。我研究的步骤分为以下几步:手淘抓包一开始我试了很多种方式去抓包,比如kill sslping、借助xposed把ssl kill掉、安装https证书等,发现依然不能抓取到网络包。最后我去反编译他的app,寻找他的网络协议。发现它的网络协议有个开...

python第三方库pil介绍_python PIL 图像处理库简介_weixin_39926402的博客-程序员宅基地

1、介绍PIL(Python Image Library)是python的第三方图像处理库,但是由于其强大的功能与众多的使用人数,几乎已经被认为是python官方图像处理库了。其官方主页为:PIL。 PIL历史悠久,原来是只支持python2.x的版本的,后来出现了移植到python3的库pillow,pillow号称是friendly fork for PIL,其功能和PIL差不多,但是支持py...

python 魔法函数_python 类 魔法函数_weixin_39924293的博客-程序员宅基地

python魔法函数python中常见的内置类型什么是魔法函数?python的魔法函数总被双下划线包围,它们可以给你的类增加特殊的方法。如果你的对象实现了这些方法中的一个,那么这个方法就会在特殊情况下被调用,你可以定义想要的行为,而这一切都是自动发生的。魔法函数一览魔法函数举例1.1.__getitem__把对象变成可迭代的对象...

为input边框添加圆角_input圆角边框_ゞ﹏铭 (り的博客-程序员宅基地

1、input标签input 标签可以为 html 网页添加一个输入框&lt;input type="text"&gt;&lt;style&gt; input { width: 300px; height: 30px; }&lt;/style&gt;2、border-radius属性border-radius可以改变边框的变圆程度&...

python iloc函数_python选取特定列 pandas iloc,loc,icol的使用详解(列切片及行切片)_weixin_39704727的博客-程序员宅基地

df是一个dataframe,列名为A B C D具体值如下:A B C D0 ss 小红 81 aa 小明 d4 f f6 ak 小紫 7dataframe里的属性是不定的,空值默认为NA。一、选取标签为A和C的列,并且选完类型还是dataframedf = df.loc[:, ['A', 'C']]df = df.iloc[:, [0, 2]]二、选取标签为C并且只取前两行,选完类型还是dat...

堆排序算法原理以及实例代码_堆排序算法及代码_cap77的博客-程序员宅基地

1、 堆排序定义 n个关键字序列Kl,K2,…,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质): (1) ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1(1≤i≤ ) 若将此序列所存储的向量R[1..n]看做是一棵完全二叉树的存储结构,则堆实质上是满足如下性质的完全二叉树:树中任一非叶结点的关键字均不大于(或不小于)其左右孩子(若存在)结点的关键字。 【例】关键字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分别满足堆性质(1)和(2),故

随便推点

比较两组数据的差异用什么图更直观_你真的懂如何展示数据吗?_weixin_39702479的博客-程序员宅基地

↑关注 + 星标~有趣的不像个技术号每晚九点,我们准时相约偶尔应金主爸爸要求改时间大家好,我是朱小五如何来展现的你的数据?是你有时不得不去思考的一个问题。不同的展示方法,其效果往往差异巨大。这里我将结合近期的一些阅读和实践,试图给出一些方法,希望能帮助到你。1. 展示之前的思考在正式开始展示数据之前,希望你去思考几个问题。这些问题将有利于你后面的一些选择。Who首先要确定,这...

C++ cin不支持录入空格_为什么cin不识别空格_Jeff_的博客-程序员宅基地

如果在C++中,用cin&gt;&gt;str;这种方法来接收字符串那么录入的str不能包含空格,否则它会按照空格将整个字符串切分成若干段。如果你要是想输入带空格的字符串那就要用到getline()这个函数了。#include &lt;iostream&gt; using namespace std;int main() { int a,b; cin&gt;&gt;...

编程语言Python为什么这么火?_python游戏研究现状_林深见鹿ing的博客-程序员宅基地

快年中了,又到了跳槽季。我明显感觉到,这段时间网上讨论职业规划和职场转型的文章多起来了。你也知道,去年疫情给很多行业带来了冲击,这让很多人更清楚地感知到了职业风险的存在。我最近和朋友聊天发现,他们普遍觉得,哪怕现在工作稳定,也应该早做职业规划,平时多充充电。说起来,职业教育领域这两年发展得特别快。我观察到,其中有一个很火爆的培训项目,就是编程语言Python。你可能还有印象,早些年一提到计算机语言,普通人听过的也就是C语言、Java之类的。但就在这几年,Python突然火了。现在一提到学编程,Pyt

D1--FPGA串口通信UART-2022.07.06_free-d协议网络版和串口版_晓晓暮雨潇潇的博客-程序员宅基地

自己的下位机驱动配合自己的上位机软件,实现下发数据被完整返回的功能,在此过程中了解相关概念,掌握重要编程细节,动手实践积累经验。

P3629 [APIO2010]巡逻(树的直径 树形dp)_0ng的博客-程序员宅基地

当k=1时答案就是树的直径头尾相连当k=2时答案就是把树的直径上的点标记起来并将相邻的两个直径上的点的距离标记为-1,再用树形dp找树的直径#include&lt;bits/stdc++.h&gt;//#define int long long#define memarray(array, value) memset(array, value, sizeof(array))using namespace std;const double EPS=1e-5;const double PI=aco