操作系统面试题 — 介绍一下几种典型的锁?

245 阅读5分钟

Author : Cyan_RA9
Source : 【卡码笔记】网站
Question : 介绍一下几种典型的锁?

【简要回答】

  1. 互斥锁(Mutex Lock)
    • 用于保护临界区,确保同一时间只有一个线程可以访问共享资源。通常与条件变量配合使用,实现线程间的条件同步。
  2. 读写锁(Read-Write Lock)
    • 允许多个读线程同时访问共享资源,但写线程必须独占资源。
  3. 自旋锁(Spin Lock)
    • 线程在获取锁时,如果锁已被占用,会一直循环检查锁的状态(忙等待),直到锁可用。

【详细回答】

  1. 互斥锁(Mutex Lock)
    • 功能:互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。
    • 实现方式:当一个线程获取锁后,其他线程必须等待锁释放后才能获取。
    • 使用场景:适用于需要独占访问共享资源的场景,如修改全局变量、访问共享数据结构等。
    • 与条件变量的配合使用:条件变量通常与互斥锁配合使用,用于实现线程间的条件同步。例如,在生产者-消费者模型中,生产者线程在条件不满足时等待,消费者线程在条件满足时通知生产者线程。
  2. 读写锁(Read-Write Lock)
    • 功能:允许多个读线程同时访问共享资源,但写线程必须独占资源。
    • 实现方式:分为共享锁(读锁)和独占锁(写锁)。读锁可以被多个线程同时持有,写锁只能被一个线程持有。
    • 使用场景:适用于读多写少的场景,如缓存系统、数据库等。
  3. 自旋锁(Spin Lock)
    • 功能:线程在获取锁时,如果锁已被占用,会一直循环检查锁的状态(忙等待),直到锁可用。
    • 实现方式:通过忙等待(Busy-Waiting)实现,避免线程切换的开销。
    • 使用场景:适用于锁占用时间极短的场景,如内核中的临界区保护。

【知识图解】

  • 自旋锁(Spinlock)的工作示意图,如下所示:
    spinlock_workchart.jpg

  • 自旋与非自旋的流程图,如下图所示:
    spinlock_flowchart.jpg


【知识拓展】

信号量、锁和条件变量的关系?

  1. 信号量
    • 信号量是一种通用的同步机制,通过计数器来控制对资源的访问,信号量机制可以实现各种同步机制,包括锁。
    • 互斥锁是信号量的一个特例,通常是一个二进制信号量(只能取0或1)。
    • 信号量可以用来实现各种同步问题,如生产者-消费者问题、读者-写者问题等。
    • 是一个更高级别的概念,通常用于保护临界区,确保同一时间只有一个线程访问共享资源。
    • 互斥锁是锁的一种,通常与信号量相关联,但它本身是锁的一个具体实现。
    • 读写锁自旋锁也是锁的类型,但它们并不属于信号量的特例。
      • 读写锁允许多个读操作同时进行,但写操作是排他的。
      • 自旋锁是在获取锁失败时循环等待,而不是进入阻塞状态。
  2. 条件变量
    • 条件变量通常与互斥锁一起使用,用于线程之间的通信。
    • 它允许线程在某个条件不满足时等待,直到其他线程发出通知
    • 条件变量本身不是信号量,但可以与信号量结合使用来实现更复杂的同步逻辑。

为什么使用锁和条件变量而不是直接使用信号量?

  • 抽象层次
    • 条件变量提供了更高层次的抽象,使代码更易读、易写和易维护。
    • 信号量更底层,需要开发者手动管理计数器,容易出错。
  • 功能专门化
    • 锁专门用于保护临界区,条件变量专门用于线程间通信,它们组合使用可以更清晰地表达同步逻辑。
    • 信号量虽然功能强大,但可能需要更多的代码来实现相同的功能,并且容易出现死锁或资源争用的问题。

读写锁和自旋锁是否属于信号量的特例?

  • 虽然“互斥锁”属于信号量的特例,但读写锁自旋锁不属于信号量的特例;它们是锁的不同类型,用于不同的场景:
    1. 读写锁允许并发读取,但 exclusive 写入。
    2. 自旋锁适用于锁持有时间非常短的场景,避免线程切换的开销。

你上面说的这三种锁能不能举出一些具体的案例呢?

  1. 互斥锁(Mutex)
    • synchronized 关键字:这是 JVM 层面 提供的互斥锁。它不仅是互斥的,还是可重入的。在竞争不激烈时,它会经过偏向锁、轻量级锁的优化,最终升级为重量级锁(真正的互斥)。
    • java.util.concurrent.locks.ReentrantLock:这是 JDK 层面 提供的互斥锁。它比 synchronized 更灵活,不但支持公平锁/非公平锁选择、而且支持可中断的锁获取以及超时获取。
  2. 读写锁 (Read-Write Lock)
    • java.util.concurrent.locks.ReentrantReadWriteLock:这是Java中最经典的读写锁实现。它包含一个 ReadLock(共享锁)和一个 WriteLock(独占锁)。
  3. 自旋锁 (Spinlock)
    • Java 没有在标准库中直接提供一个名为 SpinLock 的公共类,因为自旋锁如果使用不当会严重消耗 CPU。但自旋锁的思想在 Java 中随处可见,比如在 synchronized 的实现中,当一个线程尝试获取重量级锁失败时,JVM 不会立即挂起线程,而是先让它自旋一小段时间(自适应自旋),如果这段时间内锁释放了,就避免了昂贵的内核态切换。。