Backend-Development
  • Introduce
  • 操作系统和Linux
    • 操作系统基础
      • 进程
      • 进程间通信
      • 线程 & 协程
      • 调度
      • 互斥 & 同步
      • 死锁 & 饥饿
      • 内存管理
      • 文件系统
      • IO
    • Linux
      • Linux共享内存
      • Linux进程的内存空间布局
      • 僵尸进程和孤儿进程
      • 用户态和内核态
      • Linux进程调度算法
      • 理解inode
      • Linux进程间通信
      • Linux虚拟文件系统
      • CPU亲和性
      • 零拷贝技术
      • Linux IO栈
    • Linux常考命令
      • 管道和重定向
      • 文本处理三剑客
      • 文件和目录管理
      • 进程&内存&CPU管理
      • 用户&组管理
      • 网络管理
    • Linux系统调用
      • 内存
      • 进程
    • Linux系统编程
      • Linux堆内存管理
      • pthread库
    • Shell编程
  • 网络通信与网络编程
    • 计算机网络
      • 应用层其他协议
      • 应用层之DNS协议
      • 应用层之HTTP/3协议
      • 应用层之HTTPS协议
      • 应用层之HTTP协议
      • 传输层之UDP协议
      • 传输层之TCP协议
      • 网络层其他协议
      • 网络层之IP协议
      • 数据链路层
      • 物理层
    • 网络编程
      • cookie、session、token
      • TCP的粘包问题
      • 幂等性
      • 网络IO模型
      • 多路复用IO
      • Socket编程
      • 高并发服务器
    • Linux网络编程之底层
      • 传输控制块TCB
      • TCP数据发送之tcp_sendmsg()
      • TCP选项之MSS
    • 网络安全
    • Nginx
    • Wireshark
    • Libevent
  • 数据库
    • 数据库相关概念
    • 关系数据库设计范式
    • SQL
      • 初级SQL
      • 中级SQL
      • 高级SQL
    • Redis
      • Redis数据结构
      • Redis数据类型
      • 数据持久化
      • 雪崩 & 击穿 & 穿透
      • 主从复制
      • Redis集群
    • MySQL
      • MySQL数据类型
      • 事务
      • 事务隔离
      • 存储引擎
      • MyISAM与InnoDB
      • 锁机制
      • 索引
      • 联合索引
      • 主从复制
      • MySQL集群
      • MySQL使用总结
    • MongoDB
      • 启动与停止
      • 查询
    • Memcached
  • 组成原理和体系结构
    • 定点数 & 浮点数 & 内存
    • 体系结构
  • 编译和调试
    • 编译原理
    • Gdb调试
    • 内存屏障
    • 编译器优化
    • make/Makefile
    • cmake
    • 交叉编译
    • C++单元测试
    • 单元测试之Google Test
  • 设计模式
    • 设计模式
    • “组件协作”模式
  • 其他
    • 正则表达式
      • 基本正则表达式
      • 扩展正则表达式
    • Git版本控制
      • 提交代码
      • 常用命令
    • 编码和字符集
    • Vim用法
    • 一文解“锁”
    • 无锁技术
    • 面试中的“锁”
  • 面试题
    • 计算机网络面试题
    • 操作系统面试题
    • 数据库面试题
    • 其他面试题
    • 场景题总结
    • 智力题
Powered by GitBook
On this page
  • 并发编程中的“锁”
  • “公平锁”与“非公平锁”
  • “重入锁(递归锁)”与“不可重入锁(自旋锁)”
  • “悲观锁”与“乐观锁”
  • “共享锁”与“排他锁/独占锁”——读写锁
  • “互斥锁”和“条件锁”
  • 分布式锁

Was this helpful?

  1. 其他

面试中的“锁”

总结面试中常问的“锁”。

Previous无锁技术Next计算机网络面试题

Last updated 4 years ago

Was this helpful?

锁,顾名思义就是锁住一些资源,只有我们拿到钥匙的时候,才能操作锁住的资源,在多线程的情况下,保证操作数据的正确性与一致性。

“锁”的使用场景很多,这里的资源也是一种广泛的概念,比如CPU多核共享主存,多进程或多线程共享的硬件资源和数据,数据库中的数据,分布式环境下共享的资源,等等。

由此可见,在硬件和软件层面都会实现各种“锁”机制。在硬件层面,CPU提供了原子操作、关中断、锁内存总线的机制;OS基于这几个CPU硬件机制,就能够实现锁;再基于锁,就能够实现各种各样的同步机制(信号量、消息、Barrier等等)。同时在编程语言级别,也要提供“锁”的机制来支持并发编程和实现多线程应用程序。

并发编程中的“锁”

在并发的场景中,逻辑上来说,常见的锁有:悲观锁、乐观锁、独占锁、共享锁、公平锁、非公平锁、分布式锁、自旋锁……需要说明的是,数据库本质上是一种多线程的应用程序,因此数据库中的“锁”是指具体的实现。

面试中问“锁”的几个切入点:

  1. 操作系统中的“锁”

  2. 多线程编程中,各种“锁”的特点和区别

  3. 特定编程语言中提供的“锁”机制

  4. 具体的数据库中实现的“锁”

  5. 分布式场景下的“锁”

“公平锁”与“非公平锁”

  • 公平锁:指线程在等待获取同一个锁的时候,是严格按照申请锁的时间顺序来进行的,这就意味着在程序正常运作的时候,不会有线程执行不到,而被“饿死”,但是也需要额外的机制来维护这种顺序,所以效率相对于非公平锁会差点。

  • 非公平锁:概念跟“公平锁”恰恰相反,随机线程获取锁,效率相对高。

对于公平锁,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己。对于非公平锁, 一上来就直接尝试占有锁,如果尝试失败,才会加入到等待队列。只要线程进入了等待队列,队列里面依然是FIFO的原则,跟公平锁的顺序是一样的。

“重入锁(递归锁)”与“不可重入锁(自旋锁)”

重入/递归,不可重入/自旋,虽然名字不同,但是确实是同一种锁,只是从锁的表现跟实现方式的角度来命名而已。

  • 重入锁:指的是同一线程外层函数获得所之后,内层递归函数任然能获取该锁的代码,在同一个线程外层方法获取的时候,在进入内层方法会自动获取锁。也就是说线程可以进入任何一个它已经拥有的锁所同步着的代码块。ReentrantLock 和 Synchronize 就是一个典型的非公平重入锁。

  • 不可重入锁: 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗 CPU。

“悲观锁”与“乐观锁”

  • 悲观锁:正如其名,具有强烈的独占和排他特性。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度。因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

  • 乐观锁:乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。乐观锁是为了避免数据库幻读、业务处理时间过长等原因引起数据处理错误的一种机制,但乐观锁不会刻意使用数据库本身的锁机制,而是依据数据本身来保证数据的正确性。

实现:

在数据库中,悲观锁的流程如下:

  1. 在对记录进行修改前,先尝试为该记录加上排他锁(exclusive locking)。

  2. 如果加锁失败,说明该记录正在被修改,那么当前查询可能要等待或者抛出异常。具体响应方式由开发者根据实际需要决定。

  3. 如果成功加锁,那么就可以对记录做修改,事务完成后就会解锁了。

  4. 期间如果有其他对该记录做修改或加排他锁的操作,都会等待解锁或直接抛出异常。

数据库中的行锁,表锁,读锁,写锁,以及syncronized实现的锁均为悲观锁。

乐观锁不需要借助数据库的锁机制,乐观锁的实现包括两个步骤:冲突检测和数据更新。其实现方式有一种比较典型的就是CAS(Compare and Swap),当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

“共享锁”与“排他锁/独占锁”——读写锁

悲观锁主要分为共享锁或排他锁

  • 共享锁【Shared lock】:又称为读锁,简称S锁。顾名思义,共享锁就是多个事务对于同一数据可以共享一把锁,都能访问到数据,但是只能读不能修改。

  • 排他锁【Exclusive lock】:又称为写锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果一个事务获取了一个数据行的排他锁,其他事务就不能再获取该行的其他锁,包括共享锁和排他锁,但是获取排他锁的事务是可以对数据行读取和修改。

“互斥锁”和“条件锁”

  • 互斥锁:多任务操作中,同时运行的多个任务可能需要访问同一个资源,那么线程中就有这么一把锁,限制对共享资源的访问。互斥锁只有两种状态,上锁和不上锁。强调原子性和唯一性。

  • 条件锁:条件锁强调的是条件等待而不是互斥,条件锁会阻塞当前线程,直到某个条件成立才会继续向下执行。NSCondition和信号量都可以实现条件锁。

实现方式:

  1. 基于数据库实现分布式锁;

  2. 基于缓存(Redis等)实现分布式锁;

  3. 基于Zookeeper实现分布式锁;

分布式锁

✏️
✏️