提纲:
🔥基础八股知识
原理
获取及释放
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 方法
-