D I · Java

面试官非常和蔼可亲,虽然我很水,并且他看出来我很水,但他笑得特别可爱,并且告诉我还有哪些不足和可以改进的地方。

我超喜欢这个面试官,通过这场面试我意识到我还有很多问题。之前的大部分面试要么就是问一些干巴巴的八股,要么就是问一些我完全没有涉猎过的东西,体验非常差。今天这次面试让我体验到了一场绝佳的面试。

本场面试中,面试官喜欢问“XX 是什么?请举一个具体的例子。”这样的问题。

一、算法与数据结构

LC141. 环形链表

简单题,咱直接秒杀,OK?!

这题注意要自己写链表的 Node。

二、Java 基础

1 悲观锁和乐观锁是什么?

答:悲观锁会悲观地认为共享资源的竞争性很强,因此在对共享资源进行操作之前必须先拿到共享资源的锁;乐观锁会乐观地认为共享资源的竞争性不强,所以先对共享资源进行操作再判断是否需要加锁。悲观锁适合写比较多的情况,乐观锁适合读比较多的情况。

正解:悲观锁和乐观锁都是并发控制的策略,用于在多线程或多进程环境下保护共享资源的一致性。它们在处理并发访问时采取不同的方式来防止数据冲突和数据不一致的问题。

  • 悲观锁(Pessimistic Locking):
  • 悲观锁假设在任何时候都会有其他线程尝试访问相同的资源,因此它在访问资源之前先获取锁,确保其他线程不能同时访问。这种锁定策略在整个数据访问期间都会持有锁,这可能会导致其他线程需要等待,从而降低了并发性。
  • 常见的悲观锁实现方式是使用数据库中的排他锁(例如 FOR UPDATE),它会在查询数据库时锁定选定的行,直到事务结束。悲观锁的主要优点是它可以确保数据的一致性,但代价是可能会导致较低的并发性能。
  • 乐观锁(Optimistic Locking):
  • 乐观锁假设在绝大多数情况下不会出现并发冲突,因此它在修改资源时不立即获取锁。相反,它在更新数据之前首先检查数据是否被其他线程修改过。如果其他线程已经修改了数据,乐观锁可以采取一些策略,例如放弃更新、重试更新等。
  • 在数据库中,乐观锁通常通过在表中添加版本号或时间戳字段来实现。在更新数据时,乐观锁会检查这些额外的标识字段,以确保数据没有在获取数据后被其他线程修改。
  • 乐观锁的主要优点是在无竞争的情况下不会阻塞其他线程,从而提高了并发性能。然而,如果并发冲突频繁发生,乐观锁可能会导致大量的重试操作,影响性能。

选择悲观锁还是乐观锁取决于应用的需求和具体情况。悲观锁适用于并发冲突频繁发生的情况,而乐观锁适用于并发冲突相对较少的情况。在实际应用中,可以根据业务逻辑和性能要求来选择合适的并发控制策略。

追问:悲观锁和乐观锁是怎么加锁的?

答:悲观锁就是先拿到这个资源再操作【这波就是自信瞎说】;乐观锁就是通过 MVCC 和 CAS 来实现的【这里我其实是把 MVCC 和 CAS 还有版本号机制搞混了】。

正解:

悲观锁:悲观锁的基本思想是在访问共享资源之前,先假设会发生冲突,因此在操作期间会持有锁,以阻止其他线程对该资源进行修改。悲观锁通常通过使用互斥锁(Mutex)或者读写锁(ReadWrite Lock)来实现。具体加锁的步骤如下:

  1. 线程想要访问共享资源时,先尝试获取锁。
  2. 如果锁已经被其他线程占用,那么当前线程会被阻塞,直到获取到锁。
  3. 当线程获得锁后,它可以安全地访问共享资源。
  4. 在操作完成后,线程释放锁,其他线程可以继续竞争获取锁。

乐观锁:乐观锁的基本思想是假设在操作过程中不会发生冲突,因此在线程完成操作后,再检查是否有其他线程同时修改了资源。如果有冲突,就进行适当的处理。乐观锁通常基于版本号(Versioning)或时间戳(Timestamp)来实现。具体加锁的步骤如下:

  1. 线程想要访问共享资源时,先获取资源的版本号或时间戳。
  2. 线程进行操作,不持有锁。
  3. 在更新共享资源之前,线程会再次检查版本号或时间戳是否仍然一致。
  4. 如果版本号或时间戳一致,说明在操作期间没有其他线程修改资源,线程可以安全地更新资源,并将版本号或时间戳加一。
  5. 如果版本号或时间戳不一致,说明其他线程已经修改了资源,当前线程需要处理冲突,例如重新获取资源并重试操作。

总之,悲观锁在整个操作过程中都会持有锁,而乐观锁在操作之前和之后不会持有锁,只在检查冲突时进行处理。选择使用哪种锁取决于具体的应用场景和性能需求。

追问:什么是写锁?什么是读锁?

答:【其实不知道】写锁比较简单,在操作系统里面我记得有一种举旗的方式,想拿到写锁的线程纷纷举旗,如果有多个线程举旗,就先放下来,然后再次举旗,直到场上只有一个线程举旗,那么这个线程就拿到了写锁。共享资源的数目大于 0 的时候,都是可以去拿写锁的【这里在瞎说哦】。读锁的话,就是在没有写锁的情况下,所有想要读锁的线程都能拿到读锁。当共享资源有读锁的时候,就不可以再有写锁了。

正解:写锁(也称为排他锁)和读锁是在并发环境下对共享资源进行访问控制的两种锁类型,用于管理多个线程对资源的读取和修改操作。它们的主要区别在于允许多少个线程同时持有锁以及允许哪种类型的操作。

  • 写锁(排他锁):写锁是一种独占锁,也就是说,当一个线程持有写锁时,其他任何线程无法同时持有写锁或读锁。写锁通常用于资源的修改操作,例如更新数据。当一个线程持有写锁时,其他线程无法同时对该资源进行读取或修改。这种锁的特性确保了资源在被修改时不会被其他线程干扰。
  • 读锁:读锁是一种共享锁,允许多个线程同时持有读锁,但当有线程持有读锁时,其他线程无法持有写锁。读锁通常用于资源的读取操作,例如查询数据。由于多个线程可以同时持有读锁,这意味着多个线程可以同时读取资源,从而提高了并发性能。然而,当一个线程持有写锁时,所有的读锁和写锁都会被阻塞,以确保写操作的独占性。
  • 读写锁:在某些情况下,可以使用读写锁(也称为共享-排他锁),它允许多个线程同时持有读锁,但只允许一个线程持有写锁。这种锁机制可以在读多写少的场景中提高性能。

总结起来,写锁用于保护资源的修改操作,而读锁用于允许多个线程同时读取资源,同时阻止写操作的发生。读写锁则在读多写少的情况下平衡了这两者的需求。

p.s. 版本号机制、MVCC、CAS 的区别

版本号机制、MVCC(Multi-Version Concurrency Control)和 CAS(Compare-And-Swap)是三种在并发控制中常用的技术,用于处理多线程或多进程环境下的数据访问冲突。它们在实现方式和应用场景上有一些区别:

  • 版本号机制:版本号机制是一种乐观锁的实现方式,通常在数据库中使用。它通过在每条记录上维护一个版本号(或时间戳)来控制并发访问。在读取数据时,会读取记录的版本信息。在更新数据时,会检查当前操作是否与已读取的版本相符,如果不符则意味着其他线程已修改数据,此时更新操作可能失败,需要进行重试。这种机制可以有效减少锁冲突,提高并发性。
  • MVCC(Multi-Version Concurrency Control):MVCC 是一种数据库并发控制机制,通过为每个事务创建多个版本的数据来实现。每个事务可以看到在其开始之前提交的数据版本,这使得每个事务在隔离的情况下执行,避免了读-写冲突和写-写冲突。MVCC 通常与版本号机制结合使用,用于实现并发事务的隔离和一致性。
  • CAS(Compare-And-Swap):CAS 是一种原子操作,用于解决并发环境中的数据竞争问题。它通过比较内存中的值与预期值是否相等,如果相等则执行更新操作。CAS 是乐观锁的基础,用于实现许多并发数据结构和算法。然而,CAS 操作可能会导致自旋(循环等待),如果竞争频繁发生,可能会降低性能。

在总结上述三种技术的区别时:

版本号机制和 MVCC 主要用于数据库系统,通过维护数据的版本信息来处理并发访问冲突,实现隔离性和一致性。

CAS 是一种基础原子操作,用于解决并发环境中的数据竞争问题,常用于实现乐观锁,但可能会导致自旋。

这些技术各自适用于不同的场景,选择取决于应用的需求和性能考虑。

这里,面试官看我瞎说得厉害,就让我举写锁和读锁例子。

追问:写锁和读锁的例子?

答:数据库的行级锁就是一个写锁。读锁想不出来。

正解:文档编辑

假设有一个协作文档编辑系统,多个用户可以同时查看(读取)文档内容,但只有一个用户可以进行编辑(修改)操作。

  • 读锁(共享锁)的应用:用户 P、Q、R 需要查看文档内容时,他们可以同时持有读锁,因为查看操作不会修改文档内容,所以可以并发进行。
  • 写锁(排他锁)的应用:当用户 Y 需要编辑文档时,他需要持有写锁。在他编辑文档期间,其他用户无法同时进行编辑操作或查看操作,以确保编辑操作的独占性。

2 undo log 和 redo log

答:undo log 用于回滚,redo log 用于数据恢复。

3 Redis 有哪几种数据结构?

答:【我忘了。】有字符串(简单动态字符串)、set、hash、list。

正解:常见的有五种数据类型:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。

Redis 五种数据类型的应用场景:

  • String 类型的应用场景:缓存对象、常规计数、分布式锁、共享 session 信息等。
  • List 类型的应用场景:消息队列(但是有两个问题:1. 生产者需要自行实现全局唯一 ID;2. 不能以消费组形式消费数据)等。
  • Hash 类型:缓存对象、购物车等。
  • Set 类型:聚合计算(并集、交集、差集)场景,比如点赞、共同关注、抽奖活动等。
  • Zset 类型:排序场景,比如排行榜、电话和姓名排序等。

Redis 后续版本又支持四种数据类型,它们的应用场景如下:

  • BitMap(2.2 版新增):二值状态统计的场景,比如签到、判断用户登陆状态、连续签到用户总数等;
  • HyperLogLog(2.8 版新增):海量数据基数统计的场景,比如百万级网页 UV 计数等;
  • GEO(3.2 版新增):存储地理位置信息的场景,比如滴滴叫车;
  • Stream(5.0 版新增):消息队列,相比于基于 List 类型实现的消息队列,有这两个特有的特性:自动生成全局唯一消息ID,支持以消费组形式消费数据。

追问:底层数据结构是什么?

答:不知道🤷‍

正解:

  • String 类型的底层的数据结构实现主要是 SDS(简单动态字符串)。
  • List 类型的底层数据结构是由双向链表或压缩列表实现的
  • Hash 类型的底层数据结构是由压缩列表或哈希表实现的
  • Set 类型的底层数据结构是由哈希表或整数集合实现的
  • Zset 类型的底层数据结构是由压缩列表或跳表实现的

在 Redis 7.0 中,压缩列表数据结构已经废弃了,交由 listpack 数据结构来实现了。

详见 https://www.xiaolincoding.com/redis/base/redis_interview.html#%E4%BA%94%E7%A7%8D%E5%B8%B8%E8%A7%81%E7%9A%84-redis-%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E6%98%AF%E6%80%8E%E4%B9%88%E5%AE%9E%E7%8E%B0

追问:什么是跳表?

答:跳着查询的表。【乖巧.jpg】

正解:链表在查找元素的时候,因为需要逐一查找,所以查询效率非常低,时间复杂度是O(N),于是就出现了跳表。跳表是在链表基础上改进过来的,实现了一种「多层」的有序链表,这样的好处是能快读定位数据。

那跳表长什么样呢?我这里举个例子,下图展示了一个层级为 3 的跳表。

图中头节点有 L0~L2 三个头指针,分别指向了不同层级的节点,然后每个层级的节点都通过指针连接起来:

  • L0 层级共有 5 个节点,分别是节点1、2、3、4、5;
  • L1 层级共有 3 个节点,分别是节点 2、3、5;
  • L2 层级只有 1 个节点,也就是节点 3 。

如果我们要在链表中查找节点 4 这个元素,只能从头开始遍历链表,需要查找 4 次,而使用了跳表后,只需要查找 2 次就能定位到节点 4,因为可以在头节点直接从 L2 层级跳到节点 3,然后再往前遍历找到节点 4。

可以看到,这个查找过程就是在多个层级上跳来跳去,最后定位到元素。当数据量很大时,跳表的查找复杂度就是 O(logN)

4 什么是消息队列?

答:1. 定义:生产者、消费者和存放消息的队列。2. 功能:异步、解耦、削峰平谷。

追问:Redis 可以实现消息队列吗?

答:可以。

追问:Redis 是怎么实现消息队列的呢?

答:和生产者、消费者建立连接?【面试官(*^_^*):你说的和正确答案有点差异。】

正解:在 Redis 中实现消息队列通常是基于其数据结构和命令的组合,主要使用 List 数据类型和相关的操作来模拟消息队列的行为。以下是使用 Redis 实现消息队列的基本步骤:

  • 选择合适的数据结构: Redis 的 List 数据类型非常适合用来实现消息队列。你可以将 List 视为一个有序的字符串数组,支持在列表的两端进行添加和移除元素。
  • 生产者将消息推入队列: 生产者向 Redis 中的某个 List(通常称为队列)中推入消息,这相当于将消息放入队列的尾部。使用 Redis 命令 RPUSH 可以将消息添加到列表的右端。
  • 消费者从队列中获取消息: 消费者从队列的另一端(通常是头部)获取消息,这相当于从队列的头部弹出消息。使用 Redis 命令 LPOP 可以从列表的左端弹出并获取第一个消息。
  • 处理消息: 消费者从队列中获取消息后,可以进行相应的处理。处理完成后,消息被消费并从队列中移除。

通过上述步骤,你可以实现一个基本的消息队列系统。然而,需要注意的是,这种简单的实现可能存在一些问题,如消息的持久化、消息确认、重试机制等。

具体的可见https://www.cnblogs.com/hld123/p/14667675.html

TODO:这个问题太复杂了,先搁置一下吧。

追问:什么是降级?什么是限流?

正解:降级侧重于暂时关闭非核心功能,以确保核心功能的正常运行,而限流侧重于控制流量,防止系统被过多的请求压垮。

  • 降级:降级是一种应对系统压力的策略,当系统负载过高或出现异常情况时,为了保障核心功能的稳定性,可以选择暂时关闭某些非核心或耗费资源较大的功能。这可以减少系统的负载,确保核心功能的正常运行。在消息队列中,降级通常是指在消息队列出现问题或处理速度跟不上生产速度时,停止或限制往队列中放入新消息,以避免队列的积压进一步影响系统的稳定性。
  • 限流:限流是一种控制流量的策略,通过限制请求或任务的处理速率,以防止系统被过多的请求压垮。在消息队列中,限流可以用于控制消费者从队列中获取消息的速率,以防止消费者过多地消费消息,导致系统负载过高或资源耗尽。限流可以通过设置消费者的最大并发数最大消费速率等方式实现,以便在系统负载逐渐增加时,适当地控制消费者的活动

综合来看,降级和限流都是在高负载或异常情况下,保护系统稳定性的重要手段。

5 设计模式了解多少?

答:【想半天没想出来啥】责任链模式。责任链模式使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这些对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。

追问:责任链模式和中介者模式有什么联系和区别?

正解:责任链模式(Chain of Responsibility Pattern)和中介者模式(Mediator Pattern)都属于软件设计模式,它们都关注于对象之间的交互和通信,但在解决问题的方式和应用场景上有一些不同之处。

  • 责任链模式:责任链模式是一种行为型模式,其主要目的是将请求的发送者和接收者解耦,从而让多个对象都有机会处理请求。在责任链模式中,将多个对象组成链,并将请求沿着这条链传递,每个对象都有机会处理请求或将其传递给下一个对象。这种方式可以避免将请求的发送者和接收者紧密耦合在一起。
  • 中介者模式:中介者模式是一种行为型模式,它用于减少多个对象之间的直接通信,而是通过一个中介者来协调对象之间的交互。在中介者模式中,多个对象之间不直接通信,而是将通信委托给中介者来处理。这样可以减少对象之间的耦合性,提高系统的可维护性和可扩展性。

联系和区别:

  • 解耦程度: 责任链模式旨在解耦请求的发送者和接收者,使多个对象都有机会处理请求。中介者模式则旨在解耦多个对象之间的直接通信,通过中介者进行协调。
  • 参与对象: 责任链模式的链条中可以包含多个对象,每个对象都有可能处理请求。中介者模式涉及一组相互交互的对象,这些对象之间的通信由中介者处理。
  • 主要目标: 责任链模式的主要目标是找到适当的接收者来处理请求。中介者模式的主要目标是减少对象之间的直接通信,提高可维护性。
  • 适用场景: 责任链模式适用于多个对象都有机会处理同一个请求的情况。中介者模式适用于一组对象之间存在复杂的交互关系,需要通过中介者来协调通信。

总之,责任链模式和中介者模式都涉及对象之间的交互,但它们关注的焦点和应用场景有所不同。责任链模式强调请求的处理链条,而中介者模式强调通过中介者进行对象之间的协调通信。

追问:单例模式和工厂模式是什么?

正解:

  • 单例模式(Singleton Pattern) 是一种创建型设计模式,旨在确保一个类只有一个实例,并提供全局访问点以访问该实例。这种模式在需要控制某个类的实例数量,以及确保只有一个实例存在时非常有用。典型的用例包括数据库连接池、日志记录器等。
  • 工厂模式(Factory Pattern) 是一种创建型设计模式,旨在将对象的创建过程封装在一个工厂类中,从而隐藏具体对象的实例化细节,同时提供一种统一的接口来创建不同类型的对象。这使得代码更具可维护性和可扩展性,因为可以轻松添加新的产品类型而不必修改现有代码。

三、项目【个人特色,面试官会根据简历进行提问】

这里我此前一直都是介绍项目的业务场景,今天面试官教我说要同时介绍项目中运用到的技术

1 实习中用到哪些技术?

答:Kafka,Redis 这些中间件,微服务项目等。

2 为什么项目要用微服务架构?

答:与单体架构相比,微服务项目把业务进行拆分,高内聚,低耦合。

正解:微服务架构是一种软件架构设计模式,将一个应用程序拆分为一组小型、独立的服务单元,每个单元负责执行特定的业务功能。这种架构的采用有以下几个优势:

  • 灵活性和可扩展性: 微服务允许团队根据需要独立开发、部署和维护各个服务。这使得应用程序更容易扩展,因为你可以只对需要进行更改和扩展的特定服务进行操作,而不必影响整个应用。
  • 技术多样性: 每个微服务可以使用不同的技术栈,这使得团队能够选择最适合每个服务需求的技术。这种灵活性可以在不同的服务中选择最佳的编程语言、数据库和工具。
  • 独立部署和运维: 微服务允许独立部署和升级每个服务,这减少了整个应用程序的停机时间。此外,故障发生时,只影响到单个服务,而不会影响整个应用。
  • 团队协作: 微服务的拆分使得不同团队可以同时开发不同服务,这提高了团队的协作效率。每个团队可以专注于特定服务的开发和维护,而不会受到其他团队的干扰。
  • 可维护性: 单个微服务的代码库相对较小,这使得代码的理解、维护和测试更加容易。此外,可以更快地定位和修复单个服务中的问题,而不会涉及整个应用的复杂性。

自治性: 微服务架构鼓励每个服务团队对其服务的完全负责,从开发到部署再到运维。这种自主性可以促使团队更加关注其服务的质量和性能。

然而,微服务架构也有一些挑战,例如分布式系统的复杂性、服务间通信的管理等。因此,在决定是否采用微服务架构时,需要仔细权衡其优缺点,并根据项目的需求和团队的能力做出决策。

追问:什么叫高内聚?什么叫低耦合?

答:高内聚指相似功能的业务尽可能聚集;低耦合指代码之间的依赖尽可能少。

正解:高内聚和低耦合是软件工程中两个重要的设计原则,旨在提高代码质量、可维护性和可扩展性。

  • 高内聚(High Cohesion):高内聚是指在一个模块(类、函数、组件等)内部,其内部元素(属性、方法、功能等)彼此关联紧密,完成一个特定的任务或者功能。模块内部的元素相互依赖,共同协作,从而形成一个独立、完整的功能单元。高内聚的模块更容易理解、测试和维护,因为相关的代码被组织在一起,修改一个功能不容易影响其他功能。
  • 低耦合(Low Coupling):低耦合是指模块之间的相互依赖关系较弱,模块之间的接口简单且相互独立。低耦合的设计可以降低模块之间的相互影响,使得系统更加灵活、易于扩展和修改。当模块之间的耦合度低,修改一个模块的实现不会波及到其他模块,提高了代码的可维护性。

在软件设计中,高内聚和低耦合是相辅相成的原则。高内聚确保模块内部功能单一且紧密相关,低耦合确保模块之间独立且不受彼此实现的影响。这两个原则有助于构建易于理解、可维护和可扩展的软件系统。

追问:高内聚低耦合举个例子?

答:个人觉得,设计模式的原则就是高内聚低耦合。早上看了中介者模式。见https://www.nowcoder.com/discuss/524180443135074304?sourceSSR=users

正解:面试官其实是想要我举一个项目里的例子,但我没想起来。

3 实习项目中哪些环节用到了中间件?

答:Redis 作为缓存,Kafka 用于异步。

追问:Redis 除了做缓存还可以做什么?

答:分布式锁。

正解:除了作为缓存,Redis(Remote Dictionary Server)还可以用于许多其他用途,它是一个功能丰富的开源内存数据存储系统。以下是一些 Redis 可以用于的常见用途:

  • 数据存储: Redis 可以用作键值存储数据库,将数据存储在内存中,以提供快速的读写访问。它支持多种数据结构,如字符串、哈希、列表、集合、有序集合等,使其适用于不同类型的数据存储需求。
  • 消息队列: Redis 的发布-订阅(Pub/Sub)功能允许多个客户端通过订阅特定的频道来接收消息。这使得 Redis 可以用作简单的消息队列系统,用于异步通信和事件驱动架构。
  • 实时统计和计数器: Redis 的数据结构能够支持高性能的计数和统计操作。例如,可以使用 Redis 的有序集合来存储和排名一些数据,比如用户分数、文章点赞数等。
  • 会话存储: 在 Web 应用中,可以使用 Redis 存储会话数据,以支持分布式环境下的会话管理,提供高性能和可扩展性。
  • 缓存击穿保护: Redis 支持设置数据的过期时间,这可以用于防止缓存击穿。在缓存中设置短暂的过期时间,避免在数据库查询未命中时,大量请求直接访问数据库,从而分散数据库压力。
  • 地理空间索引: Redis 的地理空间数据结构可以用于存储和查询地理位置信息,如坐标和位置名称,以支持附近位置的搜索和查找。
  • 分布式锁: 使用 Redis 的原子性操作,可以实现分布式锁来协调多个节点之间的资源访问。
  • 缓存预热: 在系统启动或高峰期之前,可以使用 Redis 提前加载一些热门数据,以避免在实际请求到来时发生缓存未命中。
  • 数据持久化: Redis 支持将数据持久化到磁盘,以便在重启后恢复数据。它提供了两种持久化方式:快照(snapshotting)和追加文件(append-only file)。
  • 分布式应用的共享数据: 在分布式系统中,不同节点之间可以使用 Redis 共享数据,比如配置信息、状态信息等。

总之,Redis 是一个多用途的数据存储系统,通过其灵活的数据结构和高性能的特性,可以满足许多不同类型的数据处理需求。

追问:Redis 是如何做分布式锁的?

答:不知道😳。【哎,Redis 太复杂了,我真搞不明白。】

正解:https://www.xiaolincoding.com/redis/base/redis_interview.html#%E5%A6%82%E4%BD%95%E7%94%A8-redis-%E5%AE%9E%E7%8E%B0%E5%88%86%E5%B8%83%E5%BC%8F%E9%94%81%E7%9A%84

分布式锁是用于分布式环境下并发控制的一种机制,用于控制某个资源在同一时刻只能被一个应用所使用。如下图所示:

Redis 本身可以被多个客户端共享访问,正好就是一个共享存储系统,可以用来保存分布式锁,而且 Redis 的读写性能高,可以应对高并发的锁操作场景。

Redis 的 SET 命令有个 NX 参数可以实现「key不存在才插入」,所以可以用它来实现分布式锁:

  • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
  • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。

基于 Redis 节点实现分布式锁时,对于加锁操作,我们需要满足三个条件。

  • 加锁包括了读取锁变量、检查锁变量值和设置锁变量值三个操作,但需要以原子操作的方式完成,所以,我们使用 SET 命令带上 NX 选项来实现加锁;
  • 锁变量需要设置过期时间,以免客户端拿到锁后发生异常,导致锁一直无法释放,所以,我们在 SET 命令执行时加上 EX/PX 选项,设置其过期时间;
  • 锁变量的值需要能区分来自不同客户端的加锁操作,以免在释放锁时,出现误释放操作,所以,我们使用 SET 命令设置锁变量值时,每个客户端设置的值是一个唯一值,用于标识客户端;

面试官总结:你先去了解一下分布式锁吧 (^v^)

评价

对面试官的打分:10 / 10 【有引导,有深度,和蔼可亲,题目简单,完美的面试官!】

对本人的打分: 8 / 10【略知一二的说不清楚 -1 不知道的喜欢瞎说 -1】

Java 后端面经 文章被收录于专栏

记录 Java 后端面试经验

全部评论
卧槽
点赞 回复 分享
发布于 2023-08-28 00:05 广西
有帮助,点个赞
点赞 回复 分享
发布于 2023-10-13 13:34 江苏

相关推荐

10-28 15:45
门头沟学院 C++
西南山:海康威视之前不是大规模裁员吗
点赞 评论 收藏
分享
牛客963010790号:为什么还要收藏
点赞 评论 收藏
分享
2 13 评论
分享
牛客网
牛客企业服务