当前位置:首页 > 科技  > 软件

被问到ReentrantLock你真的能答好吗?

来源: 责编: 时间:2023-10-26 17:10:11 357观看
导读一、先了解一下我们知道实现一把锁要有如下几个逻辑:锁的标识线程抢锁的逻辑线程挂起的逻辑线程存储逻辑线程释放锁的逻辑线程唤醒的逻辑我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放

一、先了解一下

我们知道实现一把锁要有如下几个逻辑:gTy28资讯网——每日最新资讯28at.com

  • 锁的标识
  • 线程抢锁的逻辑
  • 线程挂起的逻辑
  • 线程存储逻辑
  • 线程释放锁的逻辑
  • 线程唤醒的逻辑

我们在讲解AQS的时候说过AQS基本负责了实现锁的全部逻辑,唯独线程抢锁和线程释放锁的逻辑是交给子类来实现了,而ReentrantLock作为最常用的独占锁,其内部就是包含了AQS的子类实现了线程抢锁和释放锁的逻辑。gTy28资讯网——每日最新资讯28at.com

gTy28资讯网——每日最新资讯28at.com

我们在使用ReentrantLock的时候一般只会使用如下方法:gTy28资讯网——每日最新资讯28at.com

 ReentrantLock lock=new ReentrantLock(); lock.lock(); lock.unlock(); lock.tryLock(); Condition condition=lock.newCondition(); condition.await(); condition.signal(); condition.signalAll();     

二、技术架构

如果我们自己来实现一个锁,那么如何设计呢?gTy28资讯网——每日最新资讯28at.com

根据AQS的逻辑,我们写一个子类sync,这个类一定会调用父类的acquire方法进行上锁,同时重写tryAcquire方法实现自己抢锁逻辑,也一定会调用release方法进行解锁,同时重写tryRelease方法实现释放锁逻辑。gTy28资讯网——每日最新资讯28at.com

gTy28资讯网——每日最新资讯28at.com

那么ReentrantLock是怎么实现的呢?gTy28资讯网——每日最新资讯28at.com

ReentrantLock的实现的类架构如下,ReentrantLock对外提供作为一把锁应该具备的api,比如lock加锁,unlock解锁等等,而它内部真正的实现是通过静态内部类sync实现,sync是AQS的子类,是真正的锁,因为这把锁需要支持公平和非公平的特性,所以sync又有两个子类FairSync和NonfairSync分别实现公平锁和非公平锁。gTy28资讯网——每日最新资讯28at.com

gTy28资讯网——每日最新资讯28at.com

因为是否公平说的是抢锁的时候是否公平,那两个子类就要在上锁方法acquire的调用和抢锁方法tryAcquire的重写上做文章。gTy28资讯网——每日最新资讯28at.com

公平锁做了什么文章?gTy28资讯网——每日最新资讯28at.com

 static final class FairSync extends Sync {          final void lock() {            acquire(1);        }        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;        }    }

公平锁比较简单,直接调用了父级类AQS的acquire方法,因为AQS的锁默认就是公平的排队策略。gTy28资讯网——每日最新资讯28at.com

重写tryAcquire方法的逻辑为:gTy28资讯网——每日最新资讯28at.com

  • 判断当前锁是否被占用,即state是否为0
  • 如果当前锁没有被占用,然后会判断等待队列中是否有线程在阻塞等待,如果有,那就终止抢锁,如果没有,就通过cas抢锁,抢到锁返回true,没有抢到锁返回false。
  • 如果当前锁已经被占用,然后判断占用锁的线程是不是自己,如果是,就会将state加1,表示重入,返回true。如果不是自己那就是代表没有抢到锁,返回false。

公平就公平在老老实实排队。gTy28资讯网——每日最新资讯28at.com

非公平锁做了什么文章?gTy28资讯网——每日最新资讯28at.com

  static final class NonfairSync extends Sync {              final void lock() {            if (compareAndSetState(0, 1))                setExclusiveOwnerThread(Thread.currentThread());            else                acquire(1);        }        protected final boolean tryAcquire(int acquires) {            return nonfairTryAcquire(acquires);        }    }        //nonfairTryAcquire代码在父类sync里面     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;        }

非公平锁也很简单,没有直接调用了父级类AQS的acquire方法,而是先通过cas抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。gTy28资讯网——每日最新资讯28at.com

它重写tryAcquire方法的逻辑为:gTy28资讯网——每日最新资讯28at.com

  • 判断当前锁是否被占用,即state是否为0
  • 如果当前锁没有被占用,就直接通过cas抢锁(不管等待队列中有没有线程在排队),抢到锁返回true,没有抢到锁返回false。
  • 如果当前锁已经被占用,然后判断占用锁的线程是不是自己,如果是,就会将state加1,表示重入,返回true。如果不是自己那就是代表没有抢到锁,返回false。

公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。gTy28资讯网——每日最新资讯28at.com

sync类中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;        }

释放锁的逻辑如下:gTy28资讯网——每日最新资讯28at.com

  • 获取state的值,然后减1
  • 如果state为0,代表锁已经释放,清空aqs中的持有锁的线程字段的值
  • 如果state不为0,说明当前线程重入了,还需要再次释放锁
  • 将state写回

释放锁往往和抢锁逻辑是对应的,每个子类抢锁逻辑不同的话,释放锁的逻辑也会对应不同。gTy28资讯网——每日最新资讯28at.com

三、具体实现

接下来我们通过ReentrantLock的使用看下它的源码实现:gTy28资讯网——每日最新资讯28at.com

class X {            private final ReentrantLock lock = new ReentrantLock();            Condition condition1=lock.newCondition();            Condition condition2=lock.newCondition();            public void m() {                lock.lock();                try {                    if(条件1){                        condition1.await();                    }                    if(条件2){                        condition2.await();                    }                } catch (InterruptedException e) {                } finally {                    condition1.signal();                    condition2.signal();                    lock.unlock();                }            }        }

1.先看这个方法:lock.lock()

ReentrantLock类public void lock() {        sync.lock();    }
  NonfairSync 类中  final void lock() {    if (compareAndSetState(0, 1))    setExclusiveOwnerThread(Thread.currentThread());    else      acquire(1);  }
  FairSync 类中  final void lock() {      acquire(1);  }     

公平锁和非公平锁中都实现了lock方法,公平锁直接调用AQS的acquire,而非公平锁先抢锁,抢不到锁再调用AQS的acquire方法进行上锁gTy28资讯网——每日最新资讯28at.com

进入acquire方法后的逻辑我们就都知道了。gTy28资讯网——每日最新资讯28at.com

2.再看这个方法lock.unlock()

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

unlock方法内直接调用了AQS的Release方法进行解锁的逻辑,进入release方法后逻辑我们都已经知道了,这里不再往下跟。gTy28资讯网——每日最新资讯28at.com

3.最后看这个方法lock.tryLock()

这个方法中直接调用了sync中的nonfairTryAcquire方法,这个就是非公平锁实现的TryAcquire方法,只是名字改了而已。gTy28资讯网——每日最新资讯28at.com

tryLock方法的意思是尝试抢锁,是给程序员调用的尝试抢锁方法,如果抢锁成功返回true,抢锁失败返回false,当拿到抢锁失败的信号后,程序员可以做一些自己的策略。gTy28资讯网——每日最新资讯28at.com

如果没有tryLock方法,而是直接调用lock方法,就会走到acquire方法中,Acquire方法中也会调用TryAcquire方法,只不过在这里如果获取不到锁就会入队阻塞了,就没有程序员参与的可能了。gTy28资讯网——每日最新资讯28at.com

4.lock.newCondition();

在AQS那篇我们说过Condition是AQS中的条件队列,可以按条件将一批线程由不可唤醒变为可唤醒。gTy28资讯网——每日最新资讯28at.com

ReentrantLock类 public Condition newCondition() {        return sync.newCondition(); }
sync静态内部类final ConditionObject newCondition() {            return new ConditionObject();}

sync提供了创建Condition对象的方法,意味着ReentrantLock也拥有Condition的能力。gTy28资讯网——每日最新资讯28at.com

四、ReentrantLock和synchronized对比

我们下面说的ReentrantLock其实就是说AQS,因为它的同步实现主要在AQS里面。gTy28资讯网——每日最新资讯28at.com

1.实现方面

  • ReentrantLock是jdk级别实现的,其源码在jdk源码中可以查看,没有脱离java。
  • synchronized是jvm级别实现的,synchronized只是java端的一个关键字,具体逻辑实现都在jvm中。

2.性能方面

优化前的synchronized性能很差,主要表现在两个方面:gTy28资讯网——每日最新资讯28at.com

  • 因为大多数情况下对于资源的争夺并没有那么激烈,甚至于某个时刻可能只有一个线程在工作,在这种没有竞争或者竞争压力很小的情况下,如果每个线程都要进行用户态到内核态的切换其实是很耗时的。
  • jdk1.6对synchronized底层实现做了优化,优化后,在单线程以及并发不是很高的情况下通过无锁偏向和自旋锁的方式避免用户态到内核态的切换,因此性能提高了,优化后的synchronized和ReentrantLock性能差不多了。

ReentrantLock是在jdk实现的,它申请互斥量就是对锁标识state的争夺,它是通过cas方式实现。在java端实现。gTy28资讯网——每日最新资讯28at.com

对于争夺不到资源的线程依然要阻塞挂起,但凡阻塞挂起都要依赖于操作系统底层,这一步的用户态到内核态的切换是避免不了的。gTy28资讯网——每日最新资讯28at.com

因此在单线程进入代码块的时候,效率是很高的,因此我们说ReentrantLock性能高于原始的synchronized:gTy28资讯网——每日最新资讯28at.com

  • 申请互斥量:synchronized的锁其实就是争夺Monitor锁的拥有权,这个争夺过程是通过操作系统底层的互斥原语Mutex实现的,这个过程会有用户态到内核态的切换。
  • 线程阻塞挂起:没能抢到到Monitor锁拥有权的线程要阻塞挂起,阻塞挂起这个动作也是依靠操作系统实现的,这个过程也需要用户态到内核态的切换。

3.特性方面

两个都是常用的典型的独占锁。gTy28资讯网——每日最新资讯28at.com

  • ReentrantLock可重入,可中断,支持公平和非公平锁,可尝试获取锁,可以支持分组将线程由不可唤醒变为可唤醒。
  • synchronized可重入,不可中断,非公平锁,不可尝试获取锁,只支持一个或者全部线程由不可唤醒到可唤醒。

4.使用方面

synchronized不需要手动释放锁,ReentrantLock需要手动释放锁,需要考虑异常对释放锁的影响避免异常导致线程一直持有锁。gTy28资讯网——每日最新资讯28at.com

以下是两个锁的使用方式:gTy28资讯网——每日最新资讯28at.com

class X {            private final ReentrantLock lock = new ReentrantLock();            Condition condition1=lock.newCondition();            Condition condition2=lock.newCondition();            public void m() {                lock.lock();                try {                    if(1==2){                        condition1.await();                    }                    if(1==3){                        condition2.await();                    }                } catch (InterruptedException e) {                } finally {                    condition1.signal();                    condition2.signal();                    lock.unlock();                }            }        }
   class X {            private final testtest sync=new testtest();;            public void m() throws InterruptedException {                synchronized(sync){                    if(1==2){                        sync.wait();                    }                    sync.notify();                    sync.notifyAll();                }            }        }

对比代码及特性说明:gTy28资讯网——每日最新资讯28at.com

  • 两个锁都是依赖一个对象:lock和sync
  • condition和wait方法具有同样的效果,进入condition和wait的线程将陷入等待(不可唤醒状态),只有被分别调用signal和notify方法线程才会重新变为可唤醒状态,请注意是可唤醒,而不是被唤醒。
  • 可唤醒是说具备了竞争资源的资格,资源空闲后,synchronized中会在可唤醒状态的线程中随机挑选一个线程去拿锁,而ReentrantLock中不可唤醒的线程变为可唤醒状态,其实就是将条件队列中的线程搬到等待队列中排队,只有队头的才会去尝试拿锁。
  • ReentrantLock分批将线程由不可唤醒变为可唤醒也在这段代码中体现了,代码中按照不同的条件将线程放入不同的condition,每个condition就是一个组,释放的时候也可以按照不同的条件进行释放。而synchronized中进入wait的线程不能分组,释放也只能随机释放一个或者全部释放。

本文链接:http://www.28at.com/showinfo-26-15183-0.html被问到ReentrantLock你真的能答好吗?

声明:本网页内容旨在传播知识,若有侵权等问题请及时与本网联系,我们将在第一时间删除处理。邮件:2376512515@qq.com

上一篇: 是时候放弃Dockerfile了,考虑上手Buildpack吧

下一篇: 掌握Go编程中的错误处理和日志记录

标签:
  • 热门焦点
  • 5月安卓手机好评榜:魅族20 Pro夺冠

    性能榜和性价比榜之后,我们来看最后的安卓手机好评榜,数据来源安兔兔评测,收集时间2023年5月1日至5月31日,仅限国内市场。第一名:魅族20 Pro好评率:97.50%不得不感慨魅族老品牌还
  • 中国家电海外掘金正当时|出海专题

    作者|吴南南编辑|胡展嘉运营|陈佳慧出品|零态LT(ID:LingTai_LT)2023年,出海市场战况空前,中国创业者在海外纷纷摩拳擦掌,以期能够把中国的商业模式、创业理念、战略打法输出海外,他们依
  • 大厂卷向扁平化

    来源:新熵作者丨南枝 编辑丨月见大厂职级不香了。俗话说,兵无常势,水无常形,互联网企业调整职级体系并不稀奇。7月13日,淘宝天猫集团启动了近年来最大的人力制度改革,目前已形成一
  • 当家的盒马,加速谋生

    来源 | 价值星球Planet作者 | 归去来自己&ldquo;当家&rdquo;的盒马,开始加速谋生了。据盒马官微消息,盒马计划今年开放生鲜供应链,将其生鲜商品送往食堂。目前,盒马在上海已经与
  • 疑似小米14外观设计图曝光:后置相机模组变化不大

    下半年的大幕已经开启,而谁将成为下半年手机圈的主角就成为了大家关注的焦点,其中被传有望拿下新一代骁龙8 Gen3旗舰芯片的小米14系列更是备受大家瞩
  • iQOO Neo8 Pro真机谍照曝光:天玑9200+和V1+旗舰双芯加持

    去年10月,iQOO推出了iQOO Neo7系列机型,不仅搭载了天玑9000+,而且是同价位唯一一款天玑9000+直屏旗舰,一经上市便受到了用户的广泛关注。在时隔半年后,
  • 机构称Q2全球智能手机出货量同比下滑11% 苹果份额依旧第2

    7月20日消息,据外媒报道,研究机构的报告显示,由于需求下滑,今年二季度全球智能手机的出货量,同比下滑了11%,三星、苹果等主要厂商的销量,较去年同期均有下
  • Counterpoint :OPPO双旗舰战略全面落地 高端产品销量增长22%

    2023年6月30日,全球行业分析机构Counterpoint Research发布的《中国智能手机高端市场白皮书》显示,中国智能手机品牌正在寻求高质量发展,中国高端智能
  • 英特尔Xe HPG游戏显卡:拥有512EU,单风扇版本

    据10 月 30 日外媒 TheVerge 消息报道,英特尔 Xe HPG Arc Alchemist 的正面实被曝光,不仅拥有 512 EU 版显卡,还拥有 128EU 的单风扇版本。另外,这款显卡 PCB
Top