美团一面,面试官让介绍AQS原理并手写一个同步器,直接凉了

news2024/10/6 20:31:45

写在开头

今天在牛客上看到了一个帖子,一个网友吐槽美团一面上来就让手撕同步器,没整出来,结果面试直接凉凉。
在这里插入图片描述
就此联想到一周前写的一篇关于AQS知识点解析的博文,当时也曾埋下伏笔说后面会根据AQS的原理实现一个自定义的同步器,那今天就来把这个坑给填上哈。

常用的AQS架构同步器类

自定义同步器实现步骤

在上一篇文章中我们就已经提过了AQS是基于 模版方法模式 的,我们基于此的自定义同步器设计一般需要如下两步:

1. 使用者继承 AbstractQueuedSynchronizer 并重写指定的方法;
2. 将 AQS 组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。

在模版方法模式下,有个很重要的东西,那就是“钩子方法”,这是一种抽象类中的方法,一般使用 protected 关键字修饰,可以给与默认实现,空方法居多,其内容逻辑由子类实现,为什么不适用抽象方法呢?因为,抽象方法需要子类全部实现,增加大量代码冗余!

Ok,有了这层理论知识,我们去看看Java中根据AQS实现的同步工具类有哪些吧

Semaphore(信号量)

在前面我们讲过的synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,而Semaphore(信号量)可以用来控制同时访问特定资源的线程数量,它并不能保证线程安全。

我们下面来看一个关于Semaphore的使用示例:

【代码示例1】

public class Test {
    private final Semaphore semaphore;

    /**
     * 构造方法初始化信号量
     * @param limit
     */
    public Test(int limit) {
        this.semaphore = new Semaphore(limit);
    }

    public void useResource() {
        try {
            semaphore.acquire();
            // 使用资源
            System.out.println("资源use:" + Thread.currentThread().getName());
            Thread.sleep(1000); // 模拟资源使用时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
            System.out.println("资源release:" + Thread.currentThread().getName());
        }
    }

    public static void main(String[] args) {
        // 限制3个线程同时访问资源
        Test pool = new Test(3);

        for (int i = 0; i < 4; i++) {
            new Thread(pool::useResource).start();
        }
    }
}

输出:

资源use:Thread-1
资源use:Thread-0
资源use:Thread-2
资源release:Thread-0
资源release:Thread-1
资源release:Thread-2
资源use:Thread-3
资源release:Thread-3

由此结果可看出,我们成功的将同时访问共享资源的线程数限制在了不超过3个的级别,这里面涉及到了Semaphore的两个主要方法:acquire()和release()

① acquire():获取许可
跟进这个方法后,我们会发现其内部调用了AQS的一个final 方法acquireSharedInterruptibly(),这个方法中又调用了tryAcquireShared(arg)放,作为AQS中的钩子方法,这个方法的实现在Semaphore的两个静态内部类 FairSync(公平模式)NonfairSync(非公平模式) 中。
在这里插入图片描述

② release():释放许可
同样跟入这个方法,里面用了AQS的releaseShared(),而在这个方法内也毫无疑问的用了tryReleaseShared(int arg)这个钩子方法,原理同上,不再冗释。

【补充】
此外,在Semaphore中还有一个Sync的内部类,提供nonfairTryAcquireShared()自旋获取资源,以及tryReleaseShared(int releases),共享方式尝试释放资源。

除了Semaphore(信号量)外,基于AQS实现的还有CountDownLatch (倒计时器)、CyclicBarrier(循环栅栏),本来想在一篇文章中讲完的,但感觉篇幅上会非常长,遂放弃,后面分篇学习吧。

手写一个同步器!

好了,有了上面的一系列学习,我们现在来手撕一个自定义的同步器吧,原理都一样滴,开始前,先贴上AQS中的几个钩子方法,防止待会忘记,哈哈!

【钩子方法】

//独占方式。尝试获取资源,成功则返回true,失败则返回false。
protected boolean tryAcquire(int)
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryRelease(int)
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
protected int tryAcquireShared(int)
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
protected boolean tryReleaseShared(int)
//该线程是否正在独占资源。只有用到condition才需要去实现它。
protected boolean isHeldExclusively()

写一个基于AQS的互斥锁,统一时刻只允许一个线程获取资源。

步骤一:

首先,我们在第一步,我们定义一个互斥锁类OnlySyncByAQS,在类中我们同样写一个静态内部类去继承AbstractQueuedSynchronizer,在内部类中,我们重写AQS的tryAcquire方法,独占方式,尝试获取资源;重写tryRelease()尝试释放资源,这俩为主要方法!

然后我们再进一步封装成lock()与unlock()的上锁与解锁方法,并在里面通过模版方法模式,去调用AQS中的acquire()和release(),从而去调到我们对模版方法的实现。

【代码示例2】

public class OnlySyncByAQS {

    private final Sync sync = new Sync();

    /**
     * 获取许可,给资源上锁
     */
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 释放许可,解锁
     */
    public void unlock() {
        sync.release(1);
    }

    /**
     * 判断是否独占
     * @return
     */
    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    /**
     * 静态内部类,继承AQS,重写钩子方法
     */
    private static class Sync extends AbstractQueuedSynchronizer {

        /**
         * 重写AQS的tryAcquire方法,独占方式,尝试获取资源。
         */
        @Override
        protected boolean tryAcquire(int arg) {
            //CAS 尝试更改状态
            if (compareAndSetState(0, 1)) {
                //独占模式下,设置锁的持有者为当前线程,来自于AOS
                setExclusiveOwnerThread(Thread.currentThread());
                System.out.println(Thread.currentThread().getName()+"获取锁成功");
                return true;
            }
            System.out.println(Thread.currentThread().getName()+"获取锁失败");
            return false;
        }

        /**
         * 独占方式。尝试释放资源,成功则返回true,失败则返回false。
         * @param arg
         * @return
         */
        @Override
        protected boolean tryRelease(int arg) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            //置空锁的持有者
            setExclusiveOwnerThread(null);
            //改状态为0,未锁定状态
            setState(0);
            System.out.println(Thread.currentThread().getName()+"释放锁成功!");
            return true;
        }

        /**
         * 判断该线程是否正在独占资源,返回state=1
         * @return
         */
        @Override
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }
    }

}

步骤二:

第二步,我们写一个测试类去调用这个自定义的互斥锁。

【代码示例2】

public class Test {

    private OnlySyncByAQS onlySyncByAQS = new OnlySyncByAQS();

    public void use(){
        onlySyncByAQS.lock();
        try {
            //休眠1秒获取使用共享资源
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            onlySyncByAQS.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Test test = new Test();
        //多线程竞争资源,每次仅一个线程拿到锁
        for (int i = 0; i < 3; i++) {
            new Thread(()->{
                test.use();
            }).start();
        }
    }
}

输出:

Thread-0获取锁成功
Thread-1获取锁失败
Thread-2获取锁失败
Thread-1获取锁失败
Thread-1获取锁失败
Thread-0释放锁成功!
Thread-1获取锁成功
Thread-1释放锁成功!
Thread-2获取锁成功
Thread-2释放锁成功!

由输出结果可以看出作为互斥锁,每次仅一个线程可以获取到锁资源,其他线程会不断尝试获取并失败,直至该线程释放锁资源!

结尾彩蛋

如果本篇博客对您有一定的帮助,大家记得留言+点赞+收藏呀。原创不易,转载请联系Build哥!

在这里插入图片描述
如果您想与Build哥的关系更近一步,还可以关注“JavaBuild888”,在这里除了看到《Java成长计划》系列博文,还有提升工作效率的小笔记、读书心得、大厂面经、人生感悟等等,欢迎您的加入!

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1583398.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

05 Php学习:类型比较、常量

PHP 类型比较 虽然 PHP 是弱类型语言&#xff0c;但也需要明白变量类型及它们的意义&#xff0c;因为我们经常需要对 PHP 变量进行比较&#xff0c;包含松散和严格比较。 松散比较&#xff1a;使用两个等号 比较&#xff0c;只比较值&#xff0c;不比较类型。严格比较&#x…

<网络> 网络Socket编程基于TCP协议模拟简易网络通信

目录​​​​​​​ 前言&#xff1a; 一、字符串回响 &#xff08;一&#xff09;程序结构 &#xff08;二&#xff09;初始化服务器 &#xff08;三&#xff09;启动服务器 1. 处理连接请求 2. 业务处理 3. 回调函数 &#xff08;四&#xff09;填充server源文件 &…

pyqt5 QScrollArea组件

本示例中&#xff0c;演示了QScrollArea的使用&#xff0c;以及QScrollBar的样式设定&#xff0c;在代码中使用setStyleSheet设置样式&#xff0c;记得要优先设置scrollArea&#xff0c;再设置窗口的样式&#xff0c;不然QScrollBar的样式会不起作用&#xff0c;使用QSS设置没有…

【讲解下Fiddler的安装和使用】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

基于Java+SpringBoot+Vue文学名著分享系统(源码+文档+部署+讲解)

一.系统概述 随着世界经济信息化、全球化的到来和互联网的飞速发展&#xff0c;推动了各行业的改革。若想达到安全&#xff0c;快捷的目的&#xff0c;就需要拥有信息化的组织和管理模式&#xff0c;建立一套合理、动态的、交互友好的、高效的文学名著分享系统。当前的信息管理…

接口自动化测试(python+pytest+requests)

一、选取自动化测试用例 优先级高:先实现业务流程用例、后实现单接口用例功能较稳定的接口优先开展测试用例脚本的实现二、搭建自动化测试环境 核心技术:编程语言:python;测试框架:pytest;接口请求:requests安装/验证requests:命令行终端分别输入 pip install requests / p…

LMDoply部署实战

使用LMDeoply部署各类开源大模型&#xff0c;进行推理实践。 一. 环境准备 1. 创建Conda环境 studio-conda -t lmdeploy -o pytorch-2.1.2 2. 安装LMDeploy 激活刚刚创建的虚拟环境。 conda activate lmdeploy 安装0.3.0版本的lmdeploy。 pip install lmdeploy[all]0.3.…

媒体邀约专访如何深入的做一篇专访报道?流程分享

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 进行媒体邀约专访并深入撰写一篇专访报道是一个系统性工作&#xff0c;涉及多个环节。以下是一个详细的流程分享&#xff1a; 一、前期准备 确定专访目的与主题&#xff1a;明确专访希…

Python程序设计 列表

教学案例八 列表 1. 计算并显示斐波那契数列 输入n,计算并显示斐波那契数列前n项.一行打印5项&#xff0c;每项显示宽度为6 什么是斐波那契数列 斐波那契数列&#xff08;Fibonacci sequence&#xff09;&#xff0c;又称黄金分割数列、 因数学家莱昂纳多斐波那契&#xff…

C++典型例题切入数据存储

一、典例导入 int globalVar 1; static int staticGlobalVar 1; void Test() {static int staticVar 1;int localVar 1;int num1[10] {1, 2, 3, 4};char char2[] "abcd";char* pChar3 "abcd";int* ptr1 (int*)malloc(sizeof (int)*4);int* ptr2 …

C语言面试题之返回倒数第 k 个节点

返回倒数第 k 个节点 实例要求 1、实现一种算法&#xff0c;找出单向链表中倒数第 k 个节点&#xff1b;2、返回该节点的值&#xff1b; 示例&#xff1a;输入&#xff1a; 1->2->3->4->5 和 k 2 输出&#xff1a; 4 说明&#xff1a;给定的 k 保证是有效的。实…

新平台上线需要注意哪些?

最近有很多被黑客攻击的老板问我前期平台上线安全防护方面需要注意哪些&#xff1f;下面就给大家讲一下。1、如果前期不打算上高防产品&#xff0c;数据一定要做好备份&#xff0c;否则一旦数据被篡改或者被加密&#xff0c;恢复都是比较困难的&#xff0c;甚至都没有办法恢复。…

亚马逊国际获得AMAZON商品详情 API ,item_get-获得AMAZON商品详情

亚马逊提供了多种API和服务&#xff0c;允许开发者获取商品详情和其他相关信息。然而&#xff0c;直接使用亚马逊的API通常需要遵循一定的步骤和条件&#xff0c;包括注册为亚马逊开发者、创建应用并获取API密钥等。 请求示例&#xff0c;API接口接入Anzexi58 关于你提到的“i…

[Java基础揉碎]System类

1) exit 退出当前程序 2) arraycopy: 复制数组元素&#xff0c;比较适合底层调用&#xff0c;一般使用 Arrays.copyOf完成复制数组(Arrays.copyOf其实底层也是用的System.arraycopy, 本质是一样的) int[] src{1,2,3}; int[] dest new int[3]; System.arraycopy(src, 0, des…

想往智能驾驶方向发展,需要做哪些努力

想往智能驾驶方向发展&#xff0c;需要做哪些努力 附赠自动驾驶学习资料和量产经验&#xff1a;链接 目前自动驾驶主要涉及到以下几个方面&#xff1a; 1.传感器技术&#xff08;激光雷达&#xff0c;毫米波雷达&#xff0c;摄像头&#xff0c;超声波雷达&#xff09;&#x…

【笔试】02

TCP TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、可靠的、基于字节流的传输层通信协议 它能够提供以下服务&#xff1a; 可靠传输 通过序列号、确认应答、重传机制等确保数据完整、准确地从发送端传输到接收端。 三次握手&#xff1a; 点对点全双工面向字节流…

final、finally、finalize的区别

final final就是不可变的意思&#xff0c;可以修饰变量、⽅法和类。修饰变量时&#xff0c;这个变量必须初始 化&#xff0c;所以也称为常量。 被final修饰的类不能被继承 如果一个变量被final修饰&#xff0c;就需要初始化。可以在声明的时候赋值&#xff0c;或者在每一个构…

Linux_进程的优先级环境变量上下文切换优先级队列

文章目录 一、进程的优先级二、进程的四个重要概念三、上下文切换四、环境变量查看当前shell环境下的环境变量与内容 五、Linux2.6内核进程调度队列一个CPU拥有一个runqueue优先级活动队列过期队列active指针和expired指针 一、进程的优先级 什么是优先级&#xff1f; 指定一个…

第十一届蓝桥杯省赛真题(C/C++大学B组)

试题A &#xff1a;门牌制作 #include <bits/stdc.h> using namespace std;const int N 100000; int arr[N];int main() {int ans 0,t;for(int i 1;i < 2020;i){t i;while(t > 0){if(t % 10 2) ans;t / 10;}}cout<<ans<<endl;return 0; } 试题B …

返回头节点的问题

当需要对链表进行操作又需要返回头节点时&#xff0c;又可能被更改的节点正是头节点 解决方法&#xff1a; 让一个指针事先指向头节点&#xff0c;然后从该指针开始操作就好了 class Solution { public:ListNode* removeNthFromEnd(ListNode* head, int n) {if(headNULL)ret…