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

面试中如何答好:ReentrantLock

来源: 责编: 时间:2023-10-13 14:37:50 179观看
导读先了解一下读本篇前,一定要确保已经读过本公众号的AQS讲解。我们知道实现一把锁要有如下几个逻辑锁的标识线程抢锁的逻辑线程挂起的逻辑线程存储逻辑线程释放锁的逻辑线程唤醒的逻辑我们在讲解AQS的时候说过AQS基本负

先了解一下

读本篇前,一定要确保已经读过本公众号的AQS讲解。a7f28资讯网——每日最新资讯28at.com

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

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

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

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

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

技术架构

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

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

图片图片a7f28资讯网——每日最新资讯28at.com

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

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

图片图片a7f28资讯网——每日最新资讯28at.com

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

公平锁做了什么文章?a7f28资讯网——每日最新资讯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的锁默认就是公平的排队策略。a7f28资讯网——每日最新资讯28at.com

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

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

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

非公平锁做了什么文章?a7f28资讯网——每日最新资讯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抢锁,它不管等待队列中有没有其他线程在排队,直接抢锁,这就体现了不公平。a7f28资讯网——每日最新资讯28at.com

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

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

公平锁和非公平分别重写了tryAcquire方法,来满足公平和非公平的特性。那么tryAcquire方法也是需要子类重写的,因为它和是否公平无关,因此tryAcquire方法被抽象到sync类中重写。a7f28资讯网——每日最新资讯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;        }

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

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

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

具体实现

接下来我们通过ReentrantLock的使用看下它的源码实现a7f28资讯网——每日最新资讯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();                }            }        }

先看这个方法: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方法进行上锁a7f28资讯网——每日最新资讯28at.com

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

再看这个方法lock.unlock()

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

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

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

public boolean tryLock(long timeout, TimeUnit unit)            throws InterruptedException {        return sync.tryAcquireNanos(1, unit.toNanos(timeout));    }

tryLock方法直接调用sync的tryAcquireNanos方法,看过AQS的应该知道tryAcquireNanos这个方法是父类AQS的方法,这个方法和AQS中的四个核心方法中的Acquire方法一样都是上锁的方法,无非是上锁的那几个步骤,调用tryAcquire方法尝试抢锁,抢不到锁就会进入doAcquireNanos方法。a7f28资讯网——每日最新资讯28at.com

public final boolean tryAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {        if (Thread.interrupted())            throw new InterruptedException();        return tryAcquire(arg) ||            doAcquireNanos(arg, nanosTimeout);    }

doAcquireNanos这个方法做的其实就是入队,阻塞等一系列上锁操作,逻辑和Acquire方法中差不多,但是有两点不同:a7f28资讯网——每日最新资讯28at.com

  1. 该方法支持阻塞指定时长。
  2. 该方法支持中断抛异常。

看下下面的代码a7f28资讯网——每日最新资讯28at.com

private boolean doAcquireNanos(int arg, long nanosTimeout)            throws InterruptedException {        if (nanosTimeout <= 0L)            return false;        final long deadline = System.nanoTime() + nanosTimeout;        final Node node = addWaiter(Node.EXCLUSIVE);        boolean failed = true;        try {            for (;;) {                final Node p = node.predecessor();                if (p == head && tryAcquire(arg)) {                    setHead(node);                    p.next = null; // help GC                    failed = false;                    return true;                }                nanosTimeout = deadline - System.nanoTime();                if (nanosTimeout <= 0L)                    return false;                if (shouldParkAfterFailedAcquire(p, node) &&                    nanosTimeout > spinForTimeoutThreshold)                    LockSupport.parkNanos(this, nanosTimeout);                if (Thread.interrupted())                    throw new InterruptedException();            }        } finally {            if (failed)                cancelAcquire(node);        }    }

这里的阻塞不再是LockSupport类的park方法,而是parkNanos方法,这个方法支持指定时长的阻塞,AQS正是利用这个方法实现阻塞指定时长,当自动唤醒后,循环中会判断是否超过设定时长,如果超过直接返回false跳出循环。a7f28资讯网——每日最新资讯28at.com

在阻塞期间,如果线程被中断,就会抛出异常,同样会跳出循环,外面可以通过捕获这个异常达到中断阻塞的目的。a7f28资讯网——每日最新资讯28at.com

可见ReentrantLock其实啥也没做,其tryLock方法完全是依赖AQS实现。a7f28资讯网——每日最新资讯28at.com

lock.newCondition();

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

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

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

ReentrantLock和synchronized对比

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

  1. 实现方面
    ReentrantLock是jdk级别实现的,其源码在jdk源码中可以查看,没有脱离java。
    synchronized是jvm级别实现的,synchronized只是java端的一个关键字,具体逻辑实现都在jvm中。
  2. 性能方面
    优化前的synchronized性能很差,主要表现在两个方面:
    因为大多数情况下对于资源的争夺并没有那么激烈,甚至于某个时刻可能只有一个线程在工作,在这种没有竞争或者竞争压力很小的情况下,如果每个线程都要进行用户态到内核态的切换其实是很耗时的。
    jdk1.6对synchronized底层实现做了优化,优化后,在单线程以及并发不是很高的情况下通过无锁偏向和自旋锁的方式避免用户态到内核态的切换,因此性能提高了,优化后的synchronized和ReentrantLock性能差不多了。
    ReentrantLock是在jdk实现的,它申请互斥量就是对锁标识state的争夺,它是通过cas方式实现。在java端实现。
    对于争夺不到资源的线程依然要阻塞挂起,但凡阻塞挂起都要依赖于操作系统底层,这一步的用户态到内核态的切换是避免不了的。
    因此在单线程进入代码块的时候,效率是很高的,因此我们说ReentrantLock性能高于原始的synchronized
  • 申请互斥量
    synchronized的锁其实就是争夺Monitor锁的拥有权,这个争夺过程是通过操作系统底层的互斥原语Mutex实现的,这个过程会有用户态到内核态的切换。
  • 线程阻塞挂起
    没能抢到到Monitor锁拥有权的线程要阻塞挂起,阻塞挂起这个动作也是依靠操作系统实现的,这个过程也需要用户态到内核态的切换。
  1. 特性方面
    两个都是常用的典型的独占锁。
    ReentrantLock可重入,可中断,支持公平和非公平锁,可尝试获取锁,可以支持分组将线程由不可唤醒变为可唤醒。
    synchronized可重入,不可中断,非公平锁,不可尝试获取锁,只支持一个或者全部线程由不可唤醒到可唤醒。
  2. 使用方面

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

以下是两个锁的使用方式a7f28资讯网——每日最新资讯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();                }            }        }

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

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


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

本文链接:http://www.28at.com/showinfo-26-13582-0.html面试中如何答好:ReentrantLock

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

上一篇: Java中的Volatile到底是什么?

下一篇: 你真的了解Django Model吗?十分钟入门指南!

标签:
  • 热门焦点
  • 小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    小米降噪蓝牙耳机Necklace分享:听一首歌 读懂一个故事

    在今天下午的小米Civi 2新品发布会上,小米还带来了一款新的降噪蓝牙耳机Necklace,我们也在发布结束的第一时间给大家带来这款耳机的简单分享。现在大家能见到最多的蓝牙耳机
  • 2023年Q2用户偏好榜:12+256G版本成新主流

    2023年Q2用户偏好榜:12+256G版本成新主流

    3月份的性能榜、性价比榜和好评榜之后,就要轮到2023年的第二季度偏好榜了,上半年的新机潮已经过去,最明显的肯定就是大内存和存储的机型了,另外部分中端机也取消了屏幕塑料支架
  • K8S | Service服务发现

    K8S | Service服务发现

    一、背景在微服务架构中,这里以开发环境「Dev」为基础来描述,在K8S集群中通常会开放:路由网关、注册中心、配置中心等相关服务,可以被集群外部访问;图片对于测试「Tes」环境或者
  • 从 Pulsar Client 的原理到它的监控面板

    从 Pulsar Client 的原理到它的监控面板

    背景前段时间业务团队偶尔会碰到一些 Pulsar 使用的问题,比如消息阻塞不消费了、生产者消息发送缓慢等各种问题。虽然我们有个监控页面可以根据 topic 维度查看他的发送状态,
  • 从零到英雄:高并发与性能优化的神奇之旅

    从零到英雄:高并发与性能优化的神奇之旅

    作者 | 波哥审校 | 重楼作为公司的架构师或者程序员,你是否曾经为公司的系统在面对高并发和性能瓶颈时感到手足无措或者焦头烂额呢?笔者在出道那会为此是吃尽了苦头的,不过也得
  • 微软邀请 Microsoft 365 商业用户,测试视频编辑器 Clipchamp

    微软邀请 Microsoft 365 商业用户,测试视频编辑器 Clipchamp

    8 月 1 日消息,微软近日宣布即将面向 Microsoft 365 商业用户,开放 Clipchamp 应用,邀请用户通过该应用来编辑视频。微软于 2021 年收购 Clipchamp,随后开始逐步整合到 Microsof
  • 阿里大调整

    阿里大调整

    来源:产品刘有媒体报道称,近期淘宝天猫集团启动了近年来最大的人力制度改革,涉及员工绩效、层级体系等多个核心事项,目前已形成一个初步的&ldquo;征求意见版&rdquo;:1、取消P序列
  • 年轻人的“职场羞耻感”,无处不在

    年轻人的“职场羞耻感”,无处不在

    作者:冯晓亭 陶 淘 李 欣 张 琳 马舒叶来源:燃次元&ldquo;人在职场,应该选择什么样的着装?&rdquo;近日,在网络上,一个与着装相关的帖子引发关注,在该帖子里,一位在高级写字楼亚洲金
  • iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    iQOO Neo8系列今日官宣:首发天玑9200+ 全球安卓最强芯!

    在昨日举行的的联发科新一代旗舰芯片天玑9200+的发布会上,iQOO官方也正式宣布,全新的iQOO Neo8系列新品将全球首发搭载这款当前性能最强大的移动平台
Top