【Java八股-第六期】Lock锁 - Java基础
提纲:
🔥基础八股知识
原理
获取及释放
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 方法
-
-
tryAcquireShared
-
1、获取写锁的线程被放入等待队列时,会标记节点为 Exclusive,获取读锁的线程被放入等待队列时,会标记为 Shared
-
2、在 tryAcquireShared 等待队列方法中,不断重试获取读锁,当获取到读锁时,会唤醒等待队列中所有的 Shared 节点
-
4.Semaphore
-
信号量锁,state 初始值为可允许的最大并发访问资源线程数,每一次线程获取锁都为 state - 1,当 state = 0 时阻塞获取锁的线程
二、面试八股真题🎈🎈🎈
1、Java中synchronized 和 ReentrantLock 有什么不同
-
相似点:
-
这两种同步方式有很多相似之处,它们都是加锁方式同步,而且都是阻塞式的同步,也就是说当如果一个线程获得了对象锁,进入了同步块,其他访问该同步块的线程都必须阻塞在同步块外面等待,而进行线程阻塞和唤醒的代价是比
-
剩余60%内容,订阅专栏后可继续查看/也可单篇购买
【📫专栏目录在最底部📫】 - 本专栏适合于JAVA已经入门的学生或人士,有一定的编程基础。 - 本专栏特点: 本专刊囊括了JAVA、Spring、计算机网路、操作系统、计算机网络、MySQL、算法与数据结构、中间件等一系列知识点,总结出了高频面试考点(附有答案),事半功倍,为大家春秋招助力。 - 本专栏内容分为五章