Java并发编程 第八章 共享模型之工具

news/2024/9/25 13:37:31/

1. AQS原理

        aqs全称是 AbstractQueuedSynchronizer,是阻塞式锁和相关的同步器工具的框架
        特点:
        用 state 属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁
        getState - 获取 state 状态
        setState - 设置 state 状态
        compareAndSetState - cas 机制设置 state 状态
        独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源
        提供了基于 FIFO 的等待队列,类似于 Monitor 的 EntryList
        条件变量来实现等待、唤醒机制,支持多个条件变量,类似于 Monitor 的 WaitSet。

子类主要实现这样一些方法(默认抛出 UnsupportedOperationException)
tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively

2.实现不可重入锁


    自定义同步器

java">// 独占锁,同步器类class MySync extends AbstractQueuedSynchronizer {@Overrideprotected boolean tryAcquire(int arg) {if (compareAndSetState(0, 1)) {//加上了锁,并设置owner为当前线程setExclusiveOwnerThread(Thread.currentThread());return true;}return false;}@Overrideprotected boolean tryRelease(int arg) {setExclusiveOwnerThread(null);setState(0);//可以加写屏障return true;}@Override// 是否持有独占锁protected boolean isHeldExclusively() {return getState()==1;}public Condition newCondition() {return new ConditionObject();}}

 自定义锁


有了自定义同步器,很容易复用 AQS ,实现一个功能完备的自定义锁

java">// 自定义锁(不可重入锁)
class MyLock implements Lock {private MySync sync = new MySync();@Override // 加锁(不成功会进入等待队列)public void lock() {sync.acquire(1);}@Override // 加锁,可打断public void lockInterruptibly() throws InterruptedException {sync.acquireInterruptibly(1);}@Override// 尝试加锁(只尝试一次)public boolean tryLock() {return sync.tryAcquire(1);}@Override// 尝试加锁带超时(只尝试一次)public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {return sync.tryAcquireNanos(1, unit.toNanos(time));}@Override// 解锁public void unlock() {sync.release(1);}@Override// 创建条件变量public Condition newCondition() {return sync.newCondition();}
}

 测试

java">@Slf4j(topic = "c.TestAqs")
public class TestAqs {public static void main(String[] args) {MyLock lock = new MyLock();new Thread(() -> {lock.lock();try {log.debug("locking...");Sleeper.sleep(1);} finally {log.debug("unlocking...");lock.unlock();}}, "t1").start();new Thread(() -> {lock.lock();try {log.debug("locking...");}finally {log.debug("unlocking...");lock.unlock();}},"t2").start();}
}

 

 

3.  ReentrantLock 原理

3.1 非公平锁实现原理

从构造器看,默认非公平实现

java">        /*** Performs lock.  Try immediate barge, backing up to normal* acquire on failure.*/final void lock() {if (compareAndSetState(0, 1))setExclusiveOwnerThread(Thread.currentThread());elseacquire(1);}

跟上文实现流程基本一样。

没有竞争时

第一个竞争出现时。

 

此时源代码中的compareAndSetState(0, 1)肯定就失败了, 进入else分支的acquire(1);acquire源码如下

java">public final void acquire(int arg) {if (!tryAcquire(arg) &&acquireQueued(addWaiter(Node.EXCLUSIVE), arg))selfInterrupt();}

首先会再尝试获取锁,如果成功则取反为false,后面的代码就不执行了,不过本例中tryAcquire(arg)肯定是失败的,所以继续判断后面的acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这是 addWaiter 逻辑,构造 Node 队列图中黄色三角表示该 Node 的 waitStatus 状态,其中 0 为默认正常状态。Node 的创建是懒惰的
其中第一个 Node 称为 Dummy(哑元)或哨兵,用来占位,并不关联线程。

当前线程进入 acquireQueued 逻辑
1. acquireQueued 会在一个死循环中不断尝试获得锁,失败后进入 park 阻塞
2. 如果自己是紧邻着 head(排第二位),那么再次 tryAcquire 尝试获取锁,当然这时 state 仍为 1,失败
3. 进入 shouldParkAfterFailedAcquire 逻辑,将前驱 node,即 head 的 waitStatus 改为 -1(改为-1表示有责任唤醒后继节点),这次返回 false。源码如下 

java">final boolean acquireQueued(final Node node, int arg) {boolean failed = true;try {boolean interrupted = false;for (;;) {final Node p = node.predecessor();if (p == head && tryAcquire(arg)) {setHead(node);p.next = null; // help GCfailed = false;return interrupted;}if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt())interrupted = true;}} finally {if (failed)cancelAcquire(node);}}

4. shouldParkAfterFailedAcquire 执行完毕回到 acquireQueued ,再次 tryAcquire 尝试获取锁,当然这时state 仍为 1,失败
5. 当再次进入 shouldParkAfterFailedAcquire 时,这时因为其前驱 node 的 waitStatus 已经是 -1,这次返回true
6. 进入 parkAndCheckInterrupt, Thread-1 park(灰色表示)

再次有多个线程经历上述过程竞争失败,变成这个样子 

 接下来进入解锁  Thread-0 释放锁,进入 tryRelease 流程,如果成功
       1.设置 exclusiveOwnerThread 为 null
        2.state = 0

java">public void unlock() {sync.release(1);}
public final boolean release(int arg) {if (tryRelease(arg)) {Node h = head;if (h != null && h.waitStatus != 0)unparkSuccessor(h);return true;}return false;}
protected final boolean tryRelease(int releases) {int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;if (c == 0) {free = true;setExclusiveOwnerThread(null);}setState(c);return free;}

当前队列不为 null,并且 head 的 waitStatus = -1,进入 unparkSuccessor 流程
找到队列中离 head 最近的一个 Node(没取消的),unpark 恢复其运行,本例中即为 Thread-1
回到 Thread-1 的 acquireQueued 流程 。

如果加锁成功(没有竞争),会设置
exclusiveOwnerThread 为 Thread-1,state = 1
head 指向刚刚 Thread-1 所在的 Node,该 Node 清空 Thread
原本的 head 因为从链表断开,而可被垃圾回收
如果这时候有其它线程来竞争(非公平的体现),例如这时有 Thread-4 来了 。

如果不巧又被 Thread-4 占了先
Thread-4 被设置为 exclusiveOwnerThread,state = 1
Thread-1 再次进入 acquireQueued 流程,获取锁失败,重新进入 park 阻塞 

3.2 可重入原理

java">{static final class NonfairSync extends Sync {// ...
// Sync 继承过来的方法, 方便阅读, 放在此处final boolean nonfairTryAcquire(int acquires) {final Thread current = Thread.currentThread();int c = getState();if (c == 0) {if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}}
// 如果已经获得了锁, 线程还是当前线程, 表示发生了锁重入else if (current == getExclusiveOwnerThread()) {
// state++int nextc = c + acquires;if (nextc < 0) // overflowthrow new Error("Maximum lock count exceeded");setState(nextc);return true;}return false;}// Sync 继承过来的方法, 方便阅读, 放在此处protected final boolean tryRelease(int releases) {// state--int c = getState() - releases;if (Thread.currentThread() != getExclusiveOwnerThread())throw new IllegalMonitorStateException();boolean free = false;// 支持锁重入, 只有 state 减为 0, 才释放成功if (c == 0) {free = true;setExclusiveOwnerThread(null);}}}

 调用 lock 方 法获取了锁之后,再次调用
lock,是不会再阻塞,内部直接增加重入次数 就行了,标识这个线程已经重
复获取一把锁而不需要等待锁的释放。

3.3 可打断原理

不可打断模式的未解锁时被打断只做一个标记,然后继续循环,在此模式下,即使它被打断,仍会驻留在 AQS 队列中,一直要等到获得锁后方能得知自己被打断了

而可打断模式打断直接抛异常,抛异常就会退出循环

3.4 公平锁原理

 

检查是否有前驱比较耗性能。

 3.5. 条件变量实现原理

每个条件变量其实就对应着一个等待队列,其实现类是 ConditionObject
await 流程
开始 Thread-0 持有锁,调用 await,进入 ConditionObject 的 addConditionWaiter 流程
创建新的 Node 状态为 -2(Node.CONDITION),关联 Thread-0,加入等待队列尾部

 

 

 

 

 


http://www.ppmy.cn/news/1530306.html

相关文章

零基础学Axios

Axios官网&#xff1a;Axios官网 想用Axios前需要在项目中安装axios,安装方式如下&#xff1a; 下列是axios请去方式&#xff0c;本文主要讲解post和get请求&#xff0c;其他请求和这两种请求方法相同。 1 get请求 1.1 不带请求参数 前端 后端 1.2 带请求参数 前端 写法…

YOLOv8改进,YOLOv8替换主干网络为VanillaNet( CVPR 2023 华为提出的全新轻量化架构),大幅度涨点

改进前训练结果: 改进后训练结果: 摘要 基础模型的核心理念是“更多即不同”,这一理念在计算机视觉和自然语言处理领域取得了惊人的成功。然而,变压器模型的优化挑战和固有复杂性呼唤一种向简化转变的范式。在本研究中,引入了 VanillaNet,一种拥抱设计优雅的神经网络架…

从Yargs源码学习中间件的设计

yargs中间件介绍 yargs 是一个用于解析命令行参数的流行库&#xff0c;它能帮助开发者轻松地定义 CLI&#xff08;命令行接口&#xff09;&#xff0c;并提供参数处理、命令组织、help文本自动生成等功能。今天我们来学习一下它对中间件的支持。 中间件的API详细信息&#xff0…

前端分段式渲染较长文章

实现思路&#xff1a; 1. 后端返回整篇文章。 2. JavaScript 分段处理&#xff1a;将文章按一定的字符或段落长度分割&#xff0c;然后逐步将这些段落追加到页面上。 3. 定时器或递归调用&#xff1a;使用 setInterval 或 setTimeout 来控制段落的逐步渲染。 代码实现示例 …

Python发送邮件附件全攻略:从设置到发送!

Python发送邮件附件的详细步骤&#xff1f;如何利用Python发信&#xff1f; Python作为一种强大的编程语言&#xff0c;提供了丰富的库来帮助我们自动化这一过程。AokSend将详细介绍如何使用Python发送邮件附件&#xff0c;从基础设置到实际发送&#xff0c;带你一步步掌握这一…

飞驰云联FTP替代方案:安全高效文件传输的新选择

FTP协议广泛应用各行业的文件传输场景中&#xff0c;由于FTP应用获取门槛低、使用普遍&#xff0c;因此大部分企业都习惯使用FTP进行文件传输。然而面临激增的数据量和网络安全威胁的不断演变&#xff0c;FTP在传输安全性与传输性能上有所欠缺&#xff0c;无法满足企业现在的高…

如何有效规避亚马逊测评中的砍单封号风险,保障账号安全?

近期&#xff0c;众多朋友在亚马逊平台进行测评时遭遇了订单被取消&#xff08;砍单&#xff09;甚至账号被封禁的问题&#xff0c;即便使用相同的测评系统和操作方法&#xff0c;不同用户间的体验却大相径庭。这一现象往往源于测评环境的不完善&#xff0c;一旦账号被风控系统…

mysql表逆向实体类

mysql表逆向实体类 目标框架springboot,mybatisplus package com.wql.repackage;import java.io.FileWriter; import java.io.IOException; import java.sql.*; import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List;public class EntityClas…