大家好,这里是 淇妙小屋 ,一个分享技术,分享生存的博主
以下是我的主页,各个主页同步更新优质博客,创作不易,还请大家点波关注
掘金主页
知乎主页
Segmentfault 主页
开源中国主页
后续会公布更多 MySQL,Redis,并发,JVM,分布式等面试热点常识,以及 Java 学习路线,面试重点,职业规划,面经等相干博客
转载请表明出处!
0. Lock 与 AQS 类图
1. Lock 接口
1.1 Lock 的定义
Lock 接口定义了锁的 API 操作,用于实现 java 中锁机制
public interface Lock {
// 如果锁可用,则取得锁后返回
// 如果锁不可用,那么以后线程会阻塞,直到获取锁后才会返回
void lock();
// 可中断的获取锁(取得锁的过程可中断)
// 如果锁可用,则取得锁后返回
// 如果锁不可用,那么线程会阻塞获取锁,阻塞获取锁的过程是可中断的,受到中断会抛出 InterruptedException
void lockInterruptibly() throws InterruptedException;
// 非阻塞的尝试取得锁
// 如果锁可用,取得锁后,返回 true
// 如果锁不可用,返回 false
boolean tryLock();
// 尝试获取锁(取得锁的过程可中断)
// 如果锁可用,取得锁后,返回 true
// 如果锁不可用,那么在指定工夫内会一直的尝试取得锁
// 如果胜利取得锁——返回 true
// 如果被中断——抛出 InterruptedException
// 如果超时——返回 false
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 开释锁
void unlock();
// 获取 Condition 对象,Condition 对象与以后 Lock 对象绑定,以后线程只有取得了锁
// 能力调用该 Condition 对象的 await()办法,而调用后,以后线程将开释锁,进入 Conditon 对象的期待队列中 WATING
// 当其余线程调用 Condition 对象的 signal(),才会唤醒 Condition 对象的期待队列中的 WATING 线程
Condition newCondition();}
1.2 Lock 与 sychronized 的异同
-
共同点
- Lock 的子类中有可重入锁
-
区别
-
Lock 显式的获取锁与开释锁
sychronized 隐式的获取锁与开释锁
-
Lock 只能给代码块上锁
sychronized 能够给代码块,办法上锁
-
Lock 依赖 JDK 实现
sychronized 依赖 JVM 实现
-
Lock 有独占模式与共享模式,每个模式还分偏心锁与非偏心锁
sychronized 是独占模式的非偏心锁
-
Lock 是可中断的,线程在取得锁的过程中是能够影响中断
sychronized 不可中断,线程在阻塞期待锁的开释的时候,是不会响应中断的
-
Lock 能够设定超时工夫,超时会返回
sychronized 不行
-
2. Condition 接口
2.1 Condition 接口定义
//Condition 对象——1. 相当于一个期待队列 2. 必须与一个锁实例绑定 3. 一个锁实例能够有多个 Condition 对象
// 相当于 monitor 对象的 WaitSet
// 必须持有 Condition 对象绑定的锁,才能够调用其办法
public interface Condition {// 相当于 Object.wait(),可中断
// 持有锁的线程开释锁,进入期待队列中,线程状态更变为 WATING,直到以下 2 种状况醒来
//1. 有其余线程调用期待队列的 signal()或 signalAll()
//2. 被其余线程中断
// 醒来后的线程会从期待队列挪动到锁实例的同步队列,状态由 WAITING 更改为 BLOKING
void await() throws InterruptedException;
// 同 await(),然而不可中断
void awaitUninterruptibly();
// 同 await(), 然而多了一种唤醒状况——期待超时(单位纳秒)long awaitNanos(long nanosTimeout) throws InterruptedException;
// 同 await(), 然而多了一种唤醒状况——期待超时
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 同 await(), 然而多了一种唤醒状况——期待超时
boolean awaitUntil(Date deadline) throws InterruptedException;
// 将期待队列中的首节点挪动到锁实例的同步队列,而后通过 LockSupport 唤醒线程
void signal();
// 将期待队列中的全副节点挪动到锁实例的同步队列,而后通过 LockSupport 唤醒线程
void signalAll();}
2.2 Condition 与 monitor 的区别
-
Condition——期待队列中期待的线程是可响应中断的
monitor——期待队列中期待的线程不可响应中断
-
Condition————期待队列中的线程能够指定期待到将来的某个具体工夫点
monitor——不反对
3. AQS
3.1 AQS 介绍
AbstractQueuedSynchronizer 形象队列同步器——用于构建锁或其余同步组件的根底框架
子类通过继承 AQS 并实现它的形象办法来实现锁
AQS 反对两种模式——独占模式,共享模式
3.2 AQS 构造
AOS 的外围字段——exclusiveOwnerThread
- 独占模式下,持有锁的线程
AQS 最外围字段——state
- state用于示意同步器的状态 (可称为 同步状态)
-
AQS 的不同子类对 state 的使用不同
-
对于 ReentrantLock
-
state=0——示意同步器没有被占用(没有线程持有锁)
state!=0——示意同步器已被占用(有线程正在应用锁)
-
尝试获取锁——查看 state 的值是否为 0,如果为 0 尝试用 CAS 批改其值,如果不为 0—则未获得锁
尝试开释锁——将 state 的值 CAS 批改为 0
-
-
对于 CountDownLatch,CyclicBarrier
- state 有不同的使用
-
Node 的状态——waitStatus
- CANCELLED(1):同步队列中的节点被中断或者超时
- INITIAL(0):初始化状态
- SIGNAL(-1):
- CONDITION(-2):示意节点在期待队列中
- PROPAGATE(-3):
AQS 有 5 个办法供子类锁去实现
-
独占模式
- boolean tryAcquire()
- boolean tryRelease()
- boolean IsHeldExclusively()
-
共享模式
- boolean tryAcquireShared()
- boolean tryReleaseShared()
3.3 AQS 独占模式
AQS 独占模式下
- 同步队列的首节点持有锁
- 如果线程尝试获取锁失败,那么会封装成一个 Node,而后退出同步队列,并开始自旋
- 节点从同步队列移除的条件——前继节点为首节点 and 胜利取得锁
- 节点开释锁时——会唤醒其后继节点
3.3.1 获取锁
同步队列中,只有当节点的前继节点是首节点,能力尝试获得锁,起因如下
-
首节点是获得锁的节点,首节点开释锁后,会唤醒其后继节点
后继节点被唤醒后须要查看本人的前继节点是否为首节点
- 保护同步队列的 FIFO 准则
3.3.2 开释锁
3.3.3 超时获取锁
过程根本与 获取锁 雷同
区别在于
- 每次自旋操作,在判断本人是否须要被阻塞之前,会优先判断是否已超时,如果超时了,就返回 false
-
如果须要被阻塞,还会查看残余的工夫有没有大于阈值
如果大于阈值——通过 LockSupport 让线程进行超时阻塞
LockSupport.parkNanos(this, nanosTimeout);
3.4 AQS 共享模式
3.4.1 共享模式与独占模式的区别
共享模式与独占模式的区别在于——同一时刻是否有多个线程能够同时获取到锁
- 共享模式拜访资源——其余共享式拜访容许,独占式拜访不容许
- 独占模式拜访资源——其余拜访一律不容许,被阻塞
3.4.2 共享模式取得锁
3.4.3 共享模式开释锁
4. ReentrantLock 实现 AQS 的独占模式
4.1 ReentrantLock 介绍
可重入:任意线程取得锁后可能再次获取该锁而不会被锁阻塞
ReentrantLock 实现了 AQS 的独占模式,是一个可重入锁,还分为 偏心锁 与 非偏心锁
- 偏心锁:先对锁进行获取申请的线程肯定先取得锁
- 非偏心锁
非偏心锁 的效率高于 偏心锁
非偏心锁 可能呈现 线程饥饿问题——局部线程迟迟无奈取得资源
ReentrantLock大多数办法的实现都是 Sync 及其子类来实现,ReentrantLock 只是对外裸露了接口
4.2 ReentrantLock 取得锁
4.2.1 非偏心锁
4.2.2 偏心锁
4.2.3 偏心锁与非偏心锁的不同
FairSync 和 NonfairSync 的 lock() 和 tryAcquire() 逻辑不同
- 非偏心锁在 lock()办法的开始就会尝试去通过 CAS 批改同步状态以取得锁,偏心锁不会
-
在自旋时,非偏心锁和偏心锁都会在 前继节点为同步队列首节点时 , 调用 tryAcquire()尝试获取锁
在 tryAcquire()中,如果 state 为 0,那么非偏心锁不会关怀节点在同步队列中的地位,间接尝试 CAS 批改 state 取得锁;然而非偏心锁关怀节点的地位,会查看是否有前继节点,如果有,就会放弃
上述 2 点 保障了偏心锁肯定是——先对锁进行获取申请的线程肯定先取得锁,而非偏心锁不肯定
4.3 ReentrantLock 开释锁
偏心锁开释锁与非偏心锁开释锁采纳同一个逻辑