Java锁原理剖析

1.AQS——锁的底层支持

AbstractQueuedSynchronizer抽象同步队列简称AQS,它是实现同步器的基础组件,并发包中锁的底层就是使用AQS实现的。
在这里插入图片描述

  如图所示,AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。其中Node的thread变量用来存放进入AQS队列里面的线程。
  AQS维持了一个单一的状态信息state,可以通过getState、setState、compareAndSetState函数修改其值。对于ReentrantLock的实现来说,state可以用来表示当前线程获取锁的可重入次数;对于读写锁ReentrantReadWriteLock来说,state的高16位表示读状态,也就是获取该读锁的次数,低16位表示获取到写锁的线程的可重入次数;对于semaphore来说,state用来表示当前可用信号的个数;对于CountDownlatch来说,state用来表示计数器当前的值。
  AQS有个内部类ConditionObject,用来结合锁实现线程同步。ConditionObject可以直接访问AQS对象内部的变量,比如state状态值和AQS队列。ConditionObject是条件变量,每个条件变量对应一个条件队列(单向链表队列),用来存放调用条件变量的await()方法后被阻塞的线程,这个条件队列的头、尾元素分别为firstWaiter和lastWaiter。
  对于AQS来说,线程同步的关键是对状态值state进行操作。根据state是否属于一个线程,操作state的方式分为独占方式和共享方式。在独占方式下获取和释放资源使用的方法为void acquire(int arg) void acquireInterruptibly(int arg) boolean release(int arg).
在共享方式下获取和释放资源的方法为void acquireShared(int arg) void acquireSharedInterruptibly(int arg) boolean releaseShared(int arg)
AQS——条件变量的支持
在这里插入图片描述如图所示,一个锁对应一个AQS阻塞队列,对应多个条件变量,每个条件变量有自己的一个条件队列。
当多个线程同时调用lock.lock()获取锁时,只有一个线程获取到了锁,其他线程会被转换为Node节点插入到lock锁对应的AQS阻塞队列里面,并做自旋CAS尝试获取锁。
如果获取到锁的线程又调用了条件变量的await()方法,则该线程会释放获取到的锁,并被转换为Node节点插入到条件变量对应的条件队列里面。
这时候因为调用lock.lock()方法被阻塞到AQS队列里面的一个线程会获取到被释放的锁,如果该线程也调用了条件变量的await()方法则该线程也会被放入条件变量的条件队列里面。
当另外一个线程调用条件变量的signal()或者signalAll()方法时,会把条件队列里面的一个或者全部Node节点移动到AQS的阻塞队列里面,等待时机获取锁。

2.独占锁 ReentrantLock的原理

ReentrantLock是可重入的独占锁,同时只能有一个线程可以获取该锁,其他获取该锁的线程会被阻塞而被放入该锁的AQS阻塞队列里面。
在这里插入图片描述
从类图可以看出,ReentrantLock的内部类Sync,还是由AQS来实现的,并且根据参数来决定其内部是一个公平还是非公平锁,默认是非公平锁。在这里AQS的状态值为0表示当前锁空闲,为大于等于1则说明该锁已经被占用。由于该锁是独占锁,所以某时只有一个线程可以获取该锁。

3.读写锁ReentrantReadWriteLock的原理

读写锁的内部维护了一个ReadLock和一个WriteLock,它们依赖Sync实现具体功能,而Sync继承自AQS,并且也提供了公平和非公平的实现。我们知道AQS只维护了一个state状态,而ReentrantReadWriteLock则需要维护读状态和写状态,它巧妙地使用state的高16位表示读状态,也就是获取到读锁的次数;使用低16位表示获取到写锁的线程的可重入次数,并通过CAS对其进行操作实现了读写分离,这在读多写少的场景下比较适用。

线程进入写锁的前提条件:

没有其他线程持有写锁

没有其他线程持有读锁

由于写锁是可重入锁 如果当前线程已经获取了该锁,再次获取只是简单地把可重入次数加1后直接返回

如果当前线程持有了读锁,不能获取写锁,因为读写锁不支持锁升级,具体原因看下方

线程进入读锁的前提条件

没有其他线程持有写锁

如果当前线程已经持有了写锁,可以获取读锁。但处理完后要把两个锁都释放掉。

其他线程持有读锁或没有锁,当前线程都可以获取读锁,读锁不是排它锁

读锁也是可重入锁,当前线程持有读锁后,还可以再次获取

相关问题

1.Java的读写锁中为什么读锁不能升级为写锁?

这是因为当线程申请读锁时,多线程是可以同时持有读锁的。假设持有读锁的线程都想升级为写锁,由于写锁是排它锁,那么线程需要等待其他持有读锁的线程释放读锁后,才能获取到写锁。这就容易发生死锁的情况,谁都不愿意率先放掉自己手中的锁。
但是读写锁的升级并不是不可能的,也有可以实现的方案,如果我们保证每次只有一个线程可以升级,那么就可以保证线程安全。只不过最常见的 ReentrantReadWriteLock 对此并不支持,我们可以自定义去实现一下。

2.什么是锁降级,锁降级的使用场景

锁降级就是指持有了写锁的线程,由于它一定独占了读写锁,因此可以让它继续获取读锁,当它同时持有写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
锁降级的用途是为了保证数据修改的可见性,在释放写锁的时候,如果另一个线程拿到了写锁,然后再覆盖了当前线程修改的值,这样当前线程修改的值就不可见了,加这个读锁进行锁降级是为了可以读到当前线程修改过的数据。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/452625.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

ANN(MLP) 三种预测

目录 介绍: 一、Mlp for binary classification 数据: 模型: 预测: 二、Mlp for Multiclass Classification 数据: 模型: 预测: 三、MLP for Regression 数据: 模型&a…

山西电力市场日前价格预测【2024-02-04】

日前价格预测 预测说明: 如上图所示,预测明日(2024-02-04)山西电力市场全天平均日前电价为367.56元/MWh。其中,最高日前电价为441.08元/MWh,预计出现在18:30。最低日前电价为324.52元/MWh,预计…

2.4 假期作业

const char *p; 值不能变 const (char*) p; 值不能变 char *const p; 地址不能变 const char* const p; 地址和值都不能变 char const *p; 值不能变 (char *)const p; 地址不能变 char con…

‘begin_code.h‘ file not found 问题解决办法

/usr/include/x86_64-linux-gnu/SDL2/SDL_platform.h:179:10: fatal error: ‘begin_code.h’ file not found 问题解决办法 问题 在使用SDL2库时编译程序出现如下问题: 解决办法 在Google搜索未果后,考虑到对于头文件找不到问题,可以通…

推荐系统|概要03_AB测试

文章目录 A/B测试问题流量不够用解决方案——分层实验 Holdout 机制 A/B测试 其中小流量是指对部分的用户先尝试改进的算法模型,而非全部。若为全部,如果算法模型存在问题,可能会导致用户体验差,导致用户流失,而小流量…

SpringCloud + Nacos环境下抽取Feign独立模块并支持MultipartFile

文章目录 一、前提条件和背景1. 前提2. 背景 二、Feign模块1. 依赖引入2. application.yaml配置3. 扩展支持MultipartFile4. 将media-api注册到feign 三、Media模块四、Content模块1. 引入依赖2. 启用FeignClient3. 测试 五、需要澄清的几点 一、前提条件和背景 1. 前提 已经…

【git】本地项目推送到github、合并分支的使用

1. github上创建仓库信息 点击个人头像,选择【你的仓库】 点击【新增】 填写仓库信息 2. 本地项目执行的操作 1.生成本地的git管理 (会生成一个.git的文件夹) git init 2.正常提交到暂存区,并填写提交消息 git add . git commit -m "init…

架构设计特训

一、考点分布 软件架构风格(※※※※)层次型软件架构风格(※※※※)面向服务的软件架构风格(※※※※)云原生架构风格(※※※※)质量属性与架构评估(※※※※※&#xff…

Java 推荐使用获取操作时间对象方法

Java 推荐使用获取操作时间对象方法 package com.zhong.newtime;import java.time.*;public class Test {public static void main(String[] args) {// 创建日期对象LocalDate now LocalDate.now();System.out.println(now);// 获取日期信息int year now.getYear(); …

视云闪播截图

视云闪播截图 1. 截图设置2. 热键设置3. 视频截取3.1. 保存 -> 完成 References 深度学习图像数据获取工具。 视云闪播 https://www.netposa.com/Service/Download.html 1. 截图设置 视云闪播 -> 系统设置 -> 截图设置 2. 热键设置 视云闪播 -> 系统设置 ->…

技术架构的演进之路

目录 一、常见概念 二、架构演进 2.1 单机架构 2.2 应用数据分离架构 2.3 应用服务集群架构 2.4 读写分离/主从分离架构 2.5 冷热分离架构 2.6 垂直分库架构 2.7 微服务架构 2.8 容器编排架构 三、互联网应用的架构 一、常见概念 模块(Module&#xff09…

【Linux笔记】缓冲区的概念到标准库的模拟实现

一、缓冲区 “缓冲区”这个概念相信大家或多或少都听说过,大家其实在C语言阶段就已经接触到“缓冲区”这个东西,但是相信大家在C语言阶段并没有真正弄懂缓冲区到底是个什么东西,也相信大家在C语言阶段也因为缓冲区的问题写出过各种bug。 其…