提纲:

🔥基础八股知识

  • 原理

    • 获取及释放

    • AQS 结构

  • ReentrantLock

    • tryAcquire

    • acquireQueue

    • tryRelease

    • 与 Sychronized 比较

  • ReentrantReadWriteLock

    • 写锁 tryAcquire

    • 读锁 tryAcquire

    • tryAcquireShared

  • Semaphore

    • 信号量锁

🎈面试八股真题

  • 1、Java中synchronized 和 ReentrantLock 有什么不同

  • 2、什么是线程安全

  • 3、说一说自己对于 synchronized 关键字的了解

  • 4、说说自己是怎么使用 synchronized 关键字

  • 5、锁的优化机制了解吗

  • 6、产生死锁的四个必要条件

  • 7、如何避免死锁

一、Lock

1. 原理

  • Lock 锁都是使用 AQS 同步器来实现锁,通过重写 tryAcquire 方法获取锁,通过 tryRelease 方法释放锁

  • AQS 结构

    • 1、ExclusiveOwner:表示当前持有锁的线程

    • 2、state:一个 volatile int 整形,用来表示锁的添加情况,不同的锁有不同的实现,例如 ReentrantReadWriteLock 通过将 32 的 int 分为高 16 位与低 16 位分别表示读写锁的使用情况

    • 3、AcquireQueue:等待队列,是一个双向的链表,当一个被阻塞的线程加入时,会将其前驱节点标记为 -1,表示在锁释放时,需要唤醒后继节点,利用双向链表的性质可以实现公平锁,ReentrantReadWriteLock 通过对链表添加 share/exclusive 属性来实现读写锁特性

    • 4、Condition:可以通过 AQS 获取多个 Condition 对象,每一个 Condition 对象本质上就是一个等待队列,线程调用哪一个 Condition 的 await 方法,就将线程放入哪一个等待队列,同样的在唤醒时,调用哪个 Condition 的 signal 方法,就唤醒对应的线程

2.ReentrantLock

  • tryAcquire

    • 1、当一个线程尝试获取锁,会先判断 state 是否为 0,若为 0,进行加锁,若不为 0,判断 ExclusiveOwner 是否是自己,若是自己,则发生锁重入,加锁

    • 2、加锁时,使用 CAS 的方式为 state 变量加 1,若成功,则成功获取锁/锁重入,若失败,则获取锁失败

    • 3、线程获取锁失败,会进入 acquireQueue 方法

  • acquireQueue

    • 1、在 acquireQueue 方法中,线程会在一个死循环中不断尝试获取锁,只有在成功获取锁后才会退出循环

    • 2、在循环中,获取锁失败后,会使用 shouldParkAfterFail 方法,调用 unsafe 的park 方法阻塞线程

    • 3、若在 park 的过程中被 interrupt 打断,若调用的是 lock 获取锁,是不可打断锁逻辑,线程在被打断后会重新回到 while 循环中,继续尝试获取锁,并且会将打断标记重置,保证线程可以继续被 park;若调用的是 lockInterruptbly,则是可打断逻辑,调用的 acuireInterruptibly等待队列方法,在被打断后,通过直接抛出 InterruptException 异常的方式来终止等待

  • tryRelease

    • 1、因为支持可重入锁,必须释放到 state 等于 0 才算完全释放

    • 2、释放时,会将 ExclusiveOwner 重置为 null,并唤醒 EntryList 等待队列,EntryList 等待队列中所有的标记为 -1 的节点被唤醒后都会去唤醒其后继节点,被唤醒的节点会继续进入 acquireQueue方法获取锁

    • 3、若实现公平锁,则线程在被唤醒时,会检查其是否存在前驱节点,若存在则继续阻塞与 Sychronized 比较:可打断/可重入/可公平/多 Condition 等待条件

3.ReentrantReadWriteLock

  • 写锁 tryAcquire

    • 1、检查 state 是否为 0,若为 0,添加写锁

    • 2、若不为 0,检查写锁部分是否为 0,若写锁部分为 0,表示当前有线程占用读锁,获取锁失败

      • # 因此在线程持有读锁的情况下不能获取写锁,必须先释放读锁再获取写锁,否则相当于产生了一个死锁条件

    • 3、若写锁部分不为 0,表示当前正被添加写锁,若 ExclusiveOwner 指向自己,则是写锁重入,添加写锁

    • 4、同样使用 CAS 的方式添加锁,若失败则获取锁失败

    • 5、获取锁失败执行 acuireQueue 方法,原理与 ReentrantLock 原理一致

  • 读锁 tryAcquire

    • 1、仅判断 state 写锁部分是否为 0,只要为 0 就尝试获取锁

    • 2、获取锁失败进入等待队列 tryAcquireShared 方法