当前位置: 首页 > news >正文

深入理解Java高并发编程(7) - JUC

1. AQS (AbstractQueuedSynchronized)

AQS是阻塞式锁和相关同步器工具的框架

特点

  • 通过State表示资源的状态,通过乐观锁机制来设置state的状态,防止多个线程同时修改state(这一点和longAdder里面的cellbusy是一样的)
  • 提供了FIFO的等待队列(双向链表),类似于Monitor中的EntryList
  • 支持多个条件变量,类似Monitor的WaitSet

2. ReentrantLock

特性:

  • 处于阻塞队列中可中断:通过lockInteruptibly()方法,如果没有竞争,该线程会持锁,如果有竞争进入阻塞队列,可以被其他方法用interrupt方法打断(当然lock下是不可打断模式)

  • 可重入:ReentrantLock和synchronize锁都是可重入的,同一线程多次请求同一锁不会被之前的锁阻塞。

  • 可以设置超时时间:

    • 通过tryLock方法,尝试获得锁但是不进入阻塞队列,返回boolean值表示是否获得锁,只尝试去获取一次
    • tryLock方法可以传入时间参数,表示在这个时间内一直尝试获取锁。
  • 可设置为公平锁/非公平锁

    • 默认是非公平锁,这里的非公平指的是可能会有新的线程来申请锁,当阻塞线程中的靠前的线程被唤醒,会和新来的线程竞争锁,这就是非公平锁,如果不竞争,则是公平锁。
    • 通过构造函数传入boolean变量设置公平/非公平锁
  • 支持多个条件变量。

    • 不同于synchronized,reentrantlock可以通过newCondition方法设置多个条件变量,await让线程阻塞,signal/signalAll方法唤醒单个线程/全部线程。

      ReentrantLock lock = new ReentrantLock();
      Condition c1 = lock.newCondition();
      c1.await(thread1);
      c1.singal();
      
    • await之前也需要先获取锁。

基本语法:

reentrantLock.lock();
try {//临界区
} finally {//释放锁reentratLock.unlock();
}

ReentrantLock中的Sync成员变量间接继承了AQS锁,构造函数通过设置NonFairSync/FairSync决定它是公平锁还是非公平锁。

image-20260312170724235
Sync成员变量组成

  • State
  • blockingQueue双向链表
  • exclusiveOwnerThread:相当于owner

1.锁竞争原理:

  1. 当没有竞争时候:

image-20260312171152647

  1. 当出现竞争(Thread1进来):
  • 双向链表中Node都是懒惰创建的,第一个节点是dummy节点,用来占位。

  • 当线程进入acquireQueued逻辑,会在一个死循环中不断尝试获得锁,失败后进入park阻塞,

  • 如果节点是第二位,则会再执行一次tryAcquire

    • 失败后进入shouldParkAfterFailedAcquire逻辑,将前驱node的waitStatus改为-1,表示该节点有唤醒后面节点的责任。再次进入acquireQueued中调用shouldParkAfterFailedAcquire,发现前驱节点是-1,进入parkAndCheckInterrupt,进入park。
    • 成功了,就会把当前节点设置成头节点,再把原来的dummy节点断开连接,把exclusiveOwnerThread设为当前节点(这里逻辑和进入阻塞队列被唤醒后是一样的)
    protected 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; // 帮助GCfailed = false;return interrupted;}// 获取失败 → 阻塞当前线程,直到被唤醒if (shouldParkAfterFailedAcquire(p, node) &&parkAndCheckInterrupt()) {interrupted = true;}}} finally {if (failed)cancelAcquire(node); // 取消获取,清理节点}}
    }
    

image-20260312174741424
3. 可重入原理:先判断申请锁线程是否为当前owner,通过把state每次通过CAS锁修改加acquires(值是1),反之,当释放时候就是就是每次 -1,当state为0时,再去设置owner为null。
4. 可打断原理:ReentrantLock有可打断模式和不可打断模式

  • 处于不可打断模式下,只是去设置打断标记返回结果,而不是真的去打断阻塞线程
  • 可打断模式下是主动抛出异常,就不会接着去阻塞
  1. 公平锁原理:公平锁tryAcquire中去检查AQS队列是否有前驱节点,没有才去和其他线程竞争锁
  2. 条件队列原理:条件变量中也是一个双向链表等待队列,所以唤醒时不是随机唤醒的,而是按照队列的顺序,等待的waitStatus是-2。

image-20260312181100843

3. ReentrantReadWriteLock

ReentrantReadWriteLock是一种读写锁,将读操作和写操作分离,当读操作远远高于写操作时,分离能提高性能,类似于数据库中的S锁和X锁

  • 通过readLock(),writeLock()方法分别获取读锁和写锁。
  • 读锁不会相互阻塞,当出现了写锁后会相互阻塞
  • 读锁不支持条件变量,写锁支持条件变量
  • 重入时升级不支持,获取读锁后不能获取写锁,但是重入是支持降级,也就是获得写锁后想要获取读锁。

ReentrantReadWriteLock的结构和ReentrantLock是类似的,也是state + 阻塞队列双向链表 + ExclusiveOwnerThread

原理:读写锁用都是同一个(NonfairSync/fairSync)同步器,不同于reentrantLock,state的低16位给写锁使用,高16位给读锁使用。

原理和ReentrantLock类似,但是双向链表中有了更多节点类型(读锁和写锁的类型)。设置waitStatus 为- 1也是表示该节点要唤醒后面节点。

场景:当t1先获取了写锁,后面t2来获取读锁,首先会判断t2和owner线程是否为同一个线程,因为写锁可以降级为读锁,不为同一进程则进入阻塞队列。假设后面又来了t3获取读锁,t4获取写锁

目前的情况是:

image-20260312190146281

假设是非公平锁的情况,当t1释放锁,t2会被前驱节点唤醒,然后t2来获取锁,t2会检查后继节点是否为读锁,如果是读锁会跟着一起唤醒,直到某个节点是读锁,修改state的高16位置。

image-20260312190924543

当t2,t3都释放了读锁之后(也就是所有读锁都被释放了),再去唤醒阻塞队列中的节点。

4. StampedLock

因为读写锁底层还是用了CAS锁,为了进一步优化读性能,使用StampedLock优化。

StampedLock中使用了戳(每次写操作都会更新这个戳),支持tryOptimisticRead()方法(乐观读),读取完毕之后需要做一个戳校验,如果戳校验通过,表明这段时间内没有写操作,数据可以安全使用,如果没通过,再去拿读锁来保证数据安全(这样就避免了读锁中CAS锁的消耗)。

  • StampedLock不同于ReentrantLock,它是不支持条件变量的
  • StampedLock不支持可重入

5. Semaphore 信号量

用于限制同时访问共享资源的线程上限。

适用场景:可以用于限流,访问高峰期时,让请求线程阻塞,高峰期后再释放许可。

原理:Semaphore底层也是用的Sync,本质上底层还是AQS。

竞争CAS锁修改state,每次竞争成功对state - 1,当state = 0时,线程进入阻塞队列。

当state不为0时,又通过之前一样的机制唤醒阻塞队列中节点,不同的是有点像读写锁中的唤醒,当state != 0 时候,唤醒一个后会尝试唤醒这个后继节点,看看state是不是 > 0,是的话就接着唤醒。

image-20260312211213949

6. CountdownLatch

倒计时锁,用来进行线程同步协作,等待所有线程完成倒计时。

构造参数用来初始化等待值,await()用来等待计数归零,countDown()用来让计数减1

同样有成员变量Sync同步器。

这个就可以代替之前用collection收集所有线程,批量join的用法,而且比起批量join,countdownLatch更适合搭配线程池使用。

7. cyclicbarrier -- 线程栅栏

cyclicbarrier和CountdownLatch类似,也是等待所有线程完成倒计时,不同的是countdownLatch不能复用,当所有countdown到0后就不能回复初始值。cyclicbarrier即使到0后也可以复用****

cyclicbarrier可以想象成一个栅栏,await方法拦截运行到这行的线程,等满足一定量的线程都运行到await,再让他们接着运行

http://www.jsqmd.com/news/608337/

相关文章:

  • 我用 AI 辅助开发了一系列小工具():文件提取工具诿
  • KafkaKing vs. 命令行:在Windows/Mac/Linux上,哪种消息监控方式更适合你?
  • ROS Melodic下UR3机械臂与Robotiq FT300力传感器的Gazebo仿真实战(避坑指南)
  • 移动端盗版应用推荐:awesome-piracy 手机观影下载攻略
  • 手把手教你用Arduino和CC2530 Zigbee模块DIY一个智能温室监控系统(附完整代码)
  • jCasbin实战教程:10个真实场景的权限控制实现
  • photoshop软件(好用的版本集合)
  • Qwen3-VL:30B多场景应用:飞书OKR群自动解析目标截图,生成执行计划与关键结果追踪
  • 怎么把B站视频变成MP3?B站视频转MP3格式,用这4个超方便的小技巧试试
  • AgentScope实战:从零构建企业级智能体工作流
  • 别只盯着升级!OpenSSH CVE-2025-26466漏洞的深度复现与资源耗尽攻击防御思考
  • 副业月入五万:我的技术咨询变现之路
  • Kubernetes External Secrets实战:AWS Secrets Manager完整配置指南
  • Vue3响应式布局实战:从PC到移动端的无缝适配(含TS配置避坑指南)
  • G-Helper终极指南:华硕笔记本性能调校的完整解决方案
  • Flink 系列第4篇:Flink 时间系统与 Timer 定时器实战精讲
  • 河北带车加盟物流公司怎么选?2026行业标杆名录来了 - 资讯焦点
  • Qwen3代码剖析:使用Keil5进行嵌入式端C语言核心模块的调试
  • 3个突破性步骤解决Cursor Pro使用限制:开源工具技术指南
  • andrej-karpathy-skills让LLM代码更可靠的6个方法:终极指南
  • 3步终极指南:如何用TikTokCommentScraper高效抓取评论数据?
  • 2026年京东云主机年付/月付/小时付价格整理汇总:新购、续费与升级指南来了
  • PyTorch实战:用GAN生成手写数字的完整指南
  • AI时代的算法思维:大经典排序学习疵
  • 2026河北加盟物流公司怎么选?先把货源充足的标准搞清楚 - 资讯焦点
  • 河南博物院铜门工程案例:国家级文化地标的甲级防火防盗铜门系统
  • PCB设计工具全攻略:从入门到精通的选型与实践
  • Hunyuan-MT 7B部署避坑指南:环境准备、一键命令、验证服务全流程
  • 某机构举办牛津帝国理工机器学习研讨会
  • PyTorch GPU版本安装避坑:CUDA版本选择与conda安装