可重入,可打断,公平锁,条件变量原理解读

简介: 可重入,可打断,公平锁,条件变量原理解读

可重入原理

什么是可重入:当线程请求一个由其它线程持有的对象锁时,该线程会阻塞,而当线程请求由自己持有的对象锁时,如果该锁是重入锁,请求就会成功,否则阻塞。

重入锁实现可重入性原理或机制是:每一个锁关联一个线程持有者和计数器,当计数器为 0 时表示该锁没有被任何线程持有,那么任何线程都可能获得该锁而调用相应的方法;当某一线程请求成功后,JVM会记下锁的持有线程,并且将计数器置为 1;此时其它线程请求该锁,则必须等待;而该持有锁的线程如果再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为 0,则释放该锁。具体源码如下

1. static final class NonfairSync extends Sync {
2. // ...
3. 
4. // Sync 继承过来的方法, 方便阅读, 放在此处
5. final boolean nonfairTryAcquire(int acquires) {
6. final Thread current = Thread.currentThread();
7. int c = getState();
8. if (c == 0) {
9. if (compareAndSetState(0, acquires)) {
10.                     setExclusiveOwnerThread(current);
11. return true;
12.                 }
13.             }
14. // 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入
15. else if (current == getExclusiveOwnerThread()) {
16. // state++
17. 
18. int nextc = c + acquires;
19. if (nextc < 0) // overflow
20. 
21. throw new Error("Maximum lock count exceeded");
22.                 setState(nextc);
23. return true;
24.             }
25. return false;
26.         }
27. 
28. // Sync 继承过来的方法, 方便阅读, 放在此处
29. protected final boolean tryRelease(int releases) {
30. // state--
31. int c = getState() - releases;
32. if (Thread.currentThread() != getExclusiveOwnerThread())
33. throw new IllegalMonitorStateException();
34. boolean free = false;
35. // 支持锁重入, 只有 state 减为 0, 才释放成功
36. if (c == 0) {
37.                 free = true;
38.                 setExclusiveOwnerThread(null);
39.             }
40.             setState(c);
41. return free;
42.         }
43.     }

可打断原理

不可打断模式

在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

1. 
2. // Sync 继承自 AQS
3. static final class NonfairSync extends Sync {
4. // ...
5. private final boolean parkAndCheckInterrupt() {
6. // 如果打断标记已经是 true, 则 park 会失效
7.             LockSupport.park(this);
8. // interrupted 会清除打断标记
9. return Thread.interrupted();
10.         }
11. 
12. final boolean acquireQueued(final Node node, int arg) {
13. boolean failed = true;
14. try {
15. boolean interrupted = false;
16. for (;;) {
17. final Node p = node.predecessor();
18. if (p == head && tryAcquire(arg)) {
19.                         setHead(node);
20.                         p.next = null;
21.                         failed = false;
22. // 还是需要获得锁后, 才能返回打断状态
23. return interrupted;
24.                     }
25. if (shouldParkAfterFailedAcquire(p, node) &&
26.                                     parkAndCheckInterrupt()
27.                     ) {
28. // 如果是因为 interrupt 被唤醒, 返回打断状态为 true
29.                         interrupted = true;
30.                     }
31.                 }
32.             } finally {
33. if (failed)
34.                     cancelAcquire(node);
35.             }
36.         }
37. 
38. public final void acquire(int arg) {
39. if (!tryAcquire(arg) &&
40.                             acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
41.             ) {
42. // 如果打断状态为 true
43.                 selfInterrupt();
44.             }
45.         }
46. 
47. static void selfInterrupt() {
48. // 重新产生一次中断
49.         Thread.currentThread().interrupt();
50.     }
51. }

可打断模式

可打断:本线程在等待获得锁的过程中,别的线程可以中止我的等待;

ReentrantLock不可打断模式:即使被打断,仅仅是打断标识设置为true,但是仍然线程会在AQS队列中,获得锁之后能够继续执行;

ReentrantLock可打断模式:源码层面当unpark之后,直接进入异常,抛出,不会再进入死循环;

1. static final class NonfairSync extends Sync {
2. public final void acquireInterruptibly(int arg) throws InterruptedException {
3. if (Thread.interrupted())
4. throw new InterruptedException();
5. // 如果没有获得到锁, 进入 ㈠
6. if (!tryAcquire(arg))
7.                 doAcquireInterruptibly(arg);
8.         }
9. // ㈠ 可打断的获取锁流程
10. private void doAcquireInterruptibly(int arg) throws InterruptedException {
11. final Node node = addWaiter(Node.EXCLUSIVE);
12. boolean failed = true;
13. try {
14. for (;;) {
15. final Node p = node.predecessor();
16. if (p == head && tryAcquire(arg)) {
17.                         setHead(node);
18.                         p.next = null; // help GC
19. 
20.                         failed = false;
21. return;
22.                     }
23. if (shouldParkAfterFailedAcquire(p, node) &&
24.                             parkAndCheckInterrupt()) {
25. // 在 park 过程中如果被 interrupt 会进入此
26. // 这时候抛出异常, 而不会再次进入 for (;;)
27. throw new InterruptedException();
28.                     }
29.                 }
30.             } finally {
31. if (failed)
32.                     cancelAcquire(node);
33.             }
34.         }
35.     }

公平锁实现原理

线程在获取锁时以公平的形式进行,没有线程占用锁时可以直接获取锁成功,已经有线程占用锁或者已经有人在排队时将进入队列排队等待。公平锁不会出现饥饿效应,所有的线程都有可以获取到锁,但对CPU唤醒线程的开销较大,线程越多开销越大

1. static final class FairSync extends Sync {
2. private static final long serialVersionUID = -3000897897090466540L;
3. final void lock() {
4.             acquire(1);
5.         }
6. // AQS 继承过来的方法, 方便阅读, 放在此处
7. public final void acquire(int arg) {
8. if (!tryAcquire(arg) &&
9.                             acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
10.             ) {
11.                 selfInterrupt();
12.             }
13.         }
14. // 与非公平锁主要区别在于 tryAcquire 方法的实现
15. protected final boolean tryAcquire(int acquires) {
16. final Thread current = Thread.currentThread();
17. int c = getState();
18. if (c == 0) {
19. // 先检查 AQS 队列中是否有前驱节点, 没有才去竞争
20. if (!hasQueuedPredecessors() &&
21.                         compareAndSetState(0, acquires)) {
22.                     setExclusiveOwnerThread(current);
23. return true;
24.                 }
25.             }
26. else if (current == getExclusiveOwnerThread()) {
27. int nextc = c + acquires;
28. if (nextc < 0)
29. throw new Error("Maximum lock count exceeded");
30.                 setState(nextc);
31. return true;
32.             }
33. return false;
34.         }
35. // (一) AQS 继承过来的方法, 方便阅读, 放在此处
36. public final boolean hasQueuedPredecessors() {
37. Node t = tail;
38. Node h = head;
39.             Node s;
40. // h != t 时表示队列中有 Node
41. return h != t && (
42. // (s = h.next) == null 表示队列中还有没有老二
43.                             (s = h.next) == null ||// 或者队列中老二线程不是此线程
44.                                     s.thread != Thread.currentThread()
45.                     );
46.         }
47.     }

非公平锁原理:

成员变量 sync = new NonfairSync(默认);

加锁时:compareAndSetState,尝试改变状态,成功就把Owner设置为当前线程,失败就再tryAcquire一次,还是失败,就创建一个节点对象,把线程加到等待队列(双向链表)里面去,park住当前线程;

释放锁:两种情况,一是唤醒的时候没有加锁的来竞争,唤醒head的后继结点,unpark它,然后把head节点链接到next等待的线程;二是有加锁的来竞争并且竞争成功,owner是别人,自己继续阻塞park;

条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject

每一个条件变量Condition都对应一个ConditionObject,含有firstWaite和lastWaiter指针。await:把线程加入到ConditionObject的链表中去,释放掉该线程所有的锁,把自己park住,然后唤醒下一个节点;

signal:必须要锁的持有者来调用该方法,把ConditionObject链表中的第一个线程转移到AQS的等待队列中;

await 流程

  • 开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程
  • 创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

接下来进入 AQS 的 fullyRelease 流程,释放同步器上的锁

unpark AQS 队列中的下一个节点,竞争锁,假设没有其他竞争线程,那么 Thread-1 竞争成功

park 阻塞 Thread-0

signal 流程

假设 Thread-1 要来唤醒 Thread-0

进入 ConditionObject 的 doSignal 流程,取得等待队列中第一个 Node,即 Thread-0 所在 Node

执行 transferForSignal 流程,将该 Node 加入 AQS 队列尾部,将 Thread-0 的 waitStatus 改为 0,Thread-3 的waitStatus 改为 -1

Thread-1 释放锁,进入 unlock 流程,略  



相关文章
|
NoSQL 关系型数据库 MySQL
备忘录:用Docker安装Mysql和Redis并进行外部连接
最近强哥在学习一个开源项目,由于想要在本地部署项目以便更好的进行项目预研,所以需要在本地搭建项目环境。
备忘录:用Docker安装Mysql和Redis并进行外部连接
|
8月前
|
传感器 人工智能 自动驾驶
OpenEMMA:德克萨斯开源端到端的自动驾驶多模态模型框架,基于预训练的 MLLMs,处理复杂的视觉数据,推理驾驶场景
OpenEMMA 是德州农工大学、密歇根大学和多伦多大学共同开源的端到端自动驾驶多模态模型框架,基于预训练的多模态大型语言模型处理视觉数据和复杂驾驶场景的推理。
389 13
OpenEMMA:德克萨斯开源端到端的自动驾驶多模态模型框架,基于预训练的 MLLMs,处理复杂的视觉数据,推理驾驶场景
|
9月前
|
SQL 存储 关系型数据库
数据库的行级锁与表锁?
表锁:存储引擎在SQL数据读写请求前对涉及的表加锁,分共享读锁和独占写锁,读锁阻塞写,写锁阻塞读写,易发锁冲突,并发性低。行级锁:InnoDB支持,通过索引加锁,提高并发性,但可能引起死锁,需注意索引使用,适用于避免不可重复读场景。
143 21
|
关系型数据库 MySQL 数据库
Python Stock安装与使用
Python Stock安装与使用
470 1
ReentrantLock(可重入锁)源码解读与使用
ReentrantLock(可重入锁)源码解读与使用
|
计算机视觉 Python
cv2.polylines
cv2.polylines
610 1
|
小程序 JavaScript API
小程序-uniapp:uni-app-base 项目基础配置及使用,开箱可用(一)
小程序-uniapp:uni-app-base 项目基础配置及使用,开箱可用(一)
542 0
|
监控
多线程并发之读写锁(ReentranReadWriteLock&ReadWriteLock)使用详解
多线程并发之读写锁(ReentranReadWriteLock&ReadWriteLock)使用详解
282 0
|
存储 人工智能 缓存
如何设计一个速率限制器(令牌桶/漏桶/固定窗口/滑动窗口)
如何设计一个速率限制器(令牌桶/漏桶/固定窗口/滑动窗口)
如何设计一个速率限制器(令牌桶/漏桶/固定窗口/滑动窗口)
|
存储 文字识别 Oracle
ORACLE ASMFD 配置删除磁盘
1、AFD特性 * asM FILTER DRIVER 可以最大限度的限制磁盘的用户权限,即便是root用户,也无法删除AFD磁盘 * 在asM 12.1之后支持,在12.2中,asMFD在系统已经安装了asMLIB的情况下无法使用安装AFD,也不支持扩展分区表;18c以后的版本,AFD开...
2643 0