java并发编程原理2 (AQS, ReentrantLock,线程池)
一、AQS:
1.1 AQS是什么?
AQS就是一个抽象队列同步器,abstract queued sychronizer,本质就是一个抽象类。
AQS中有一个核心属性state,其次还有一个双向链表以及一个单项链表。
首先state是基于volatile修饰,再基于CAS修改,同时可以保证三大特性。(原子,可见,有序)
其次还提供了一个双向链表。有Node对象组成的双向链表。
最后在Condition内部类中,还提供了一个由Node对象组成的单向链表。
AQS是JUC下大量工具的基础类,很多工具都基于AQS实现的,比如lock锁,CountDownLatch,Semaphore,线程池等等都用到了AQS。
state是啥:state就是一个int类型的数值,同步状态,至于到底是什么状态,看子类实现。
condition和单向链表是啥:都知道sync内部提供了wait方法和notify方法的使用,lock锁也需要实现这种机制,lock锁就基于AQS内部的Condition实现了await和signal方法。(对标sync的wait和notify)
sync在线程持有锁时,执行wait方法,会将线程扔到WaitSet等待池中排队,等待唤醒
lcok在线程持有锁时,执行await方法,会将线程封装为Node对象,扔到Condition单向链表中,等待唤醒
Condition在做了什么:将持有锁的线程封装为Node扔到Condition单向链表,同时挂起线程。如果线程唤醒了,就将Condition中的Node扔到AQS的双向链表等待获取锁。
1.2 唤醒线程时,AQS为什么从后往前遍历?
如果线程没有获取到资源,就需要将线程封装为Node对象,安排到AQS的双向链表中排队,并且可能会挂起线程
如果在唤醒线程时,head节点的next是第一个要被唤醒的,如果head的next节点取消了,AQS的逻辑是从tail节点往前遍历,找到离head最近的有效节点?
想解释清楚这个问题,需要先了解,一个Node对象,是如何添加到双向链表中的。
基于addWaiter方法中,是先将当前Node的prev指向tail的节点,再将tail指向我自己,再让prev节点指向我
如下图,如果只执行到了2步骤,此时,Node加入到了AQS队列中,但是从prev节点往后,会找不到当前节点。

1.3 AQS为什么用双向链表,(为啥不用单向链表)?
因为AQS中,存在取消节点的操作,节点被取消后,需要从AQS的双向链表中断开连接。
还需要保证双向链表的完整性,
- 需要将prev节点的next指针,指向next节点。
- 需要将next节点的prev指针,指向prev节点。
如果正常的双向链表,直接操作就可以了。
但是如果是单向链表,需要遍历整个单向链表才能完成的上述的操作。比较浪费资源。
1.4 AQS为什么要有一个虚拟的head节点
有一个哨兵节,点更方便操作。
另一个是因为AQS内部,每个Node都会有一些状态,这个状态不单单针对自己,还针对后续节点
- 1:当前节点取消了。
- 0:默认状态,啥事没有。
- -1:当前节点的后继节点,挂起了。
- -2:代表当前节点在Condition队列中(await将线程挂起了)
- -3:代表当前是共享锁,唤醒时,后续节点依然需要被唤醒。
Node节点的ws,表示很多信息,除了当前节点的状态,还会维护后继节点状态。
如果取消虚拟的head节点,一个节点无法同时保存当前阶段状态和后继节点状态。
同时,在释放锁资源时,就要基于head节点的状态是否是-1。来决定是否唤醒后继节点。
如果为-1,正常唤醒
如果不为-1,不需要唤醒吗,减少了一次可能发生的遍历操作,提升性能。
1.5 ReentrantLock的底层实现原理
ReentrantLock是基于AQS实现的。
在线程基于ReentrantLock加锁时,需要基于CAS去修改state属性,如果能从0改为1,代表获取锁资源成功
如果CAS失败了,添加到AQS的双向链表中排队(可能会挂起线程),等待获取锁。
持有锁的线程,如果执行了condition的await方法,线程会封装为Node添加到Condition的单向链表中,等待被唤醒并且重新竞争锁资源
Java中除了一会讲到的线程池中Worker的锁之外,都是可重入锁。
1.6 ReentrantLock的公平锁和非公平锁的区别
- 公平锁和非公平中的lock方法和tryAcquire方法的实现有一内内不同,其他都一样
- 非公平锁lock:直接尝试将state从 0 ~ 1,如果成功,拿锁直接走,如果失败了,执行tryAcquire
- 公平锁lock:直接执行tryAcquire
- 非公平锁tryAcquire:如果当前没有线程持有锁资源,直接再次尝试将state从 0 ~ 1如果成功,拿锁直接走
- 公平锁tryAcquire:如果当前没有线程持有锁资源,先看一下,有排队的么。
- 如果没有排队的,直接尝试将state从 0 ~ 1
- 如果有排队的,第一名不是我,不抢,继续等待。
- 如果有排队的,我是第一名,直接尝试将state从 0 ~ 1
- 如果都没拿到锁,公平锁和非公平锁的后续逻辑是一样的,排队后,就不存在所谓的插队。
生活的例子:非公平锁会有机会尝试强行获取锁资源两次,成功开开心心走人,失败,消消停停去排队。
- 有个人前来做核酸
- 公平锁:先看眼,有排队的么,有就去排队
- 非公平锁:不管什么情况,先尝试做凳子上。如果坐上了,直接被扣,扣完走人,如果没做到凳子上
- 有人正在扣嗓子眼么?
- 没人正在被扣,上去尝试做凳子上!成功了,扣完走人。
- 如果有人正在扣,消停去排队。
- 有人正在扣嗓子眼么?
1.7 ReentrantReadWriteLock如何实现的读写锁
如果一个操作写少读多,还用互斥锁的话,性能太低,因为读读不存在并发问题。
怎么解决啊,有读写锁的出现。
ReentrantReadWriteLock也是基于AQS实现的一个读写锁,但是锁资源用state标识。
如何基于一个int来标识两个锁信息,有写锁,有读锁,怎么做的?
一个int,占了32个bit位。
在写锁获取锁时,基于CAS修改state的低16位的值。
在读锁获取锁时,基于CAS修改state的高16位的值。
写锁的重入,基于state低16直接标识,因为写锁是互斥的。
读锁的重入,无法基于state的高16位去标识,因为读锁是共享的,可以多个线程同时持有。所以读锁的重入用的是ThreadLocal来表示,同时也会对state的高16为进行追加。
二、阻塞队列高频问题:
2.1 阻塞队列
ArrayBlockingQueue,LinkedBlockingQueue,PriorityBlockingQueue
ArrayBlockingQueue:底层基于数组实现,记得new的时候设置好边界。
LinkedBlockingQueue:底层基于链表实现的,可以认为是无界队列,但是可以设置长度。
PriorityBlockingQueue:底层是基于数组实现的二叉堆,可以认为是无界队列,因为数组会扩容。
ArrayBlockingQueue,LinkedBlockingQueue是ThreadPoolExecutor线程池最常用的两个阻塞队列。
PriorityBlockingQueue:是ScheduleThreadPoolExecutor定时任务线程池用的阻塞队列跟PriorityBlockingQueue的底层实现是一样的。(其实本质用的是DelayWorkQueue)
2.2 虚假唤醒
虚假唤醒在阻塞队列的源码中就有体现。
比如消费者1在消费数据时,会先判断队列是否有元素,如果元素个数为0,消费者1会挂起。
此处判断元素为0的位置,如果用if循环会导致出现一个问题。
如果生产者添加了一个数据,会唤醒消费者1。
但是如果消费者1没拿到锁资源,消费者2拿到了锁资源并带走了数据的话。
消费者1再次拿到锁资源时,无法从队列获取到任何元素。导致出现逻辑问题。
解决方案,将判断元素个数的位置,设置为while判断。
三、线程池
3.1 线程池的7个参数(不会就回家等通知)
核心线程数,最大线程数,最大空闲时间,时间单位,阻塞队列,线程工厂,拒绝策略
3.2 线程池的状态有什么,如何记录的?
线程池不是什么时候都接活的!
线程池有5个状态。、

线程池的状态是在ctl属性中记录的。本质就是int类型

ctl的高三位记录线程池状态
低29位,记录工作线程个数。即便你指定的线程最大数量是Integer.MAX_VALUE他也到不了
3.3 线程池常见的拒绝策略(不会就回家等通知)
AbortPolicy:抛异常(默认)

CallerRunsPolicy,谁提交的任务,谁执行。异步变同步

DiscardPolicy:任务直接不要

DiscardOldestPolicy:把最早放过来的任务丢失,再次尝试将当前任务交给线程池处理

一般情况下,线程池自带的无法满足业务时,自定义一个线程池的拒绝策略。
实现下面的接口即可。

3.4 线程池执行流程(不会就回家等通知)
核心线程不是new完就构建的,是懒加载的机制,添加任务才会构建核心线程
2个核心线程 5个最大线程 阻塞队列长度为2

3.5 线程池为什么添加空任务的非核心线程

避免线程池出现工作队列有任务,但是没有工作线程处理。
线程池可以设置核心线程数是0个。这样,任务扔到阻塞队列,但是没有工作线程,这不凉凉了么~~
线程池中的核心线程不是一定不会被回收,线程池中有一个属性,如果设置为true,核心线程也会被干掉

3.6 在没任务时,线程池中的工作线程在干嘛?
线程会挂起,默认核心线程是WAITING状态,非核心是TIMED_WAITING
如果是核心线程,默认情况下,会在阻塞队列的位置执行take方法,直到拿到任务为止。
如果是非核心线程,默认情况下,会在阻塞队列的位置执行poll方法,等待最大空闲时间,如果没任务,直接拉走咔嚓掉,如果有活,那就正常干。
3.7 工作线程出现异常会导致什么问题?
是否抛出异常、影响其他线程吗、工作线程会嘎嘛?
如果任务是execute方法执行的,工作线程会将异常抛出。
如果任务是submit方法执行的futureTask,工作线程会将异常捕获并保存到FutureTask里,可以基于futureTask的get得到异常信息
出现异常的工作线程不会影响到其他的工作线程。
runWorker中的异常会被抛到run方法中,run方法会异常结束,run方法结束,线程就嘎了!
如果是submit,异常没抛出来,那就不嘎~
3.8 工作线程继承AQS的目的是什么?
工作线程的本质,就是Worker对象
继承AQS跟shutdown和shutdownNow有关系。
如果是shutdown,会中断空闲的工作线程,基于Worker实现的AQS中的state的值来判断能否中断工作线程。
如果工作线程的state是0,代表空闲,可以中断,如果是1,代表正在干活。
如果是shutdownNow,直接强制中断所有工作线程
3.9 核心参数怎么设置?
线程池的目的是为了充分发挥CPU的资源。提升整个系统的性能。
系统内部不同业务的线程池参考的方式也不一样。
如果是CPU密集的任务,一般也就是CPU内核数 + 1的核心线程数。这样足以充分发挥CPU性能。
如果是IO密集的任务,因为IO的程度不一样的啊,有的是1s,有的是1ms,有的是1分钟,所以IO密集的任务在用线程池处理时,一定要通过压测的方式,观察CPU资源的占用情况,来决定核心线程数。一般发挥CPU性能到70~80足矣。所以线程池的参数设置需要通过压测以及多次调整才能得出具体的。
比如一个业务要查询三个服务
相关文章:
java并发编程原理2 (AQS, ReentrantLock,线程池)
一、AQS: 1.1 AQS是什么? AQS就是一个抽象队列同步器,abstract queued sychronizer,本质就是一个抽象类。 AQS中有一个核心属性state,其次还有一个双向链表以及一个单项链表。 首先state是基于volatile修饰&#x…...
研报精选230219
目录 【行业230219山西证券】煤炭行业周报:复工改善,港口价格企稳反弹【行业230219中航证券】农林牧渔行业周观点:一号文件落地,生物育种超势不改【行业230219华西证券】汽车行业周报:新车密集上市 自主转型提速【个股…...
【PPPoE】PPPoE拨号流程
简介 PPPoE(Point-to-Point Protocol over Ethernet)是一种在以太网上封装PPP协议的方式,常用于在宽带接入中进行拨号。 PPPoE的拨号原理如下: 客户端发起PPPoE Active Discovery Initiation (PADI)报文,广播到网络…...
django项目实战(django+bootstrap实现增删改查)
目录 一、创建django项目 二、修改默认配置 三、配置数据库连接 四、创建表结构 五、在app当中创建静态文件 六、页面实战-部门管理 1、实现一个部门列表页面 2、实现新增部门页面 3、实现删除部门 4、实现部门编辑功能 七、模版的继承 1、创建模板layout.html 1&…...
Lesson4---Python语言基础(2)
4.1 内置数据结构 4.1.1 序列数据结构(sequence) 成员是有序排列的每个元素的位置称为下标或索引通过索引访问序列中的成员Python中的序列数据类型有字符串、列表、元组 “abc” ≠ “bac” 4.1.1.1 创建列表和元组 Python中的列表和元组,…...
NCHW - NHWC - CHWN 排列
TensorFlow有两种数据格式NHWC和NCHW,默认的数据格式是NHWC,可以通过参数data_format指定数据格式。这个参数规定了 input Tensor 和 output Tensor 的排列方式。 1、data_format 设置为 “NHWC” 时,排列顺序为 [batch, height, width, channels] 设置为 “NCHW” 时,排…...
2019蓝桥杯真题矩阵切割(填空题) C语言/C++
题目描述 本题为填空题,只需要算出结果后,在代码中使用输出语句将所填结果输出即可。 小明有一些矩形的材料,他要从这些矩形材料中切割出一些正方形。 当他面对一块矩形材料时,他总是从中间切割一刀,切出一块最大的正…...
Java线程池的创建以及原理
一、为什么要使用线程池 在外面的日常开发中,也使用了不少池化技术,比如线程池、数据库连接池、HTTP连接池等等都是对这个思想的应用。 池化技术的思想主要是为了减少每次获取资源的消耗,提高对资源的利用率。 线程池提供了一种限制和管理资…...
Java集合学习之Map
1.什么是Map Java里的Map接口是一个集合根接口,表示一个 键值对(Key-Value) 的映射。 简单来说就是键和值是一对的,每一个 Key都有唯一确定的 Value对应。 其中要求 键(Key) 唯一,因为是按照…...
java 基于maven多模块合并打包部署
项目环境 jdk 1.8spring 2.7.xmaven 3.6 项目结构 模块功能 client – 对外service common – 共用工具切面等 main – 启动类 goods – 子模块具体实现 模块间依赖关系 client – 无依赖 common – 无依赖 main – client、common、goods goods – client、common 具体P…...
Kubernetes是个什么东东?
Kubernetes 是一个可移植、可扩展的开源平台,用于管理容器化的工作负载和服务,可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态,其服务、支持和工具的使用范围相当广泛。 Kubernetes 这个名字源于希腊语,意…...
【go语言grpc之client端源码分析三】
go语言grpc之server端源码分析三newClientStreamnewAttemptLocked上一篇在介绍了grpc.Dial之后,然后再介绍一下后面的 //创建RPC客户端client : pb.NewGreetsClient(conn)//设置超时时间_, cancel : context.WithTimeout(context.Background(), time.Second)defer c…...
Android 基础知识4-2.6LinearLayout(线性布局)
一、LinearLayout的概述 线性布局(LinearLayout)主要以水平或垂直方式来排列界面中的控件。并将控件排列到一条直线上。在线性布局中,如果水平排列,垂直方向上只能放一个控件,如果垂直排列,水平方向上也只能…...
补充前端面试题(三)
图片懒加载<!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" content"widthdevice-width, in…...
.net开发安卓入门-自动升级(配合.net6 webapi 作为服务端)
文章目录思路客户端权限清单(AndroidManifest.xml)权限列表(完整内容看 权限清单(AndroidManifest.xml))打开外部应用的权限(完整内容看 权限清单(AndroidManifest.xml))添加文件如下…...
分享111个HTML艺术时尚模板,总有一款适合您
分享111个HTML艺术时尚模板,总有一款适合您 111个HTML艺术时尚模板下载链接:https://pan.baidu.com/s/1sYo2IPma4rzeku3yCG7jGw?pwdk8dx 提取码:k8dx Python采集代码下载链接:采集代码.zip - 蓝奏云 时尚理发沙龙服务网站模…...
spring之Spring AOP基于注解
文章目录前言一、Spring AOP基于注解的所有通知类型1、前置通知2、后置通知3、环绕通知4、最终通知5、异常通知二、Spring AOP基于注解之切面顺序三、Spring AOP基于注解之通用切点三、Spring AOP基于注解之连接点四、Spring AOP基于注解之全注解开发前言 通知类型包括&#x…...
LeetCode题目笔记——6362. 合并两个二维数组 - 求和法
文章目录题目描述题目链接题目难度——简单方法一:常规双指针遍历代码/Python方法二:字典\哈希表代码/Python总结题目描述 给你两个 二维 整数数组 nums1 和 nums2. nums1[i] [idi, vali] 表示编号为 idi 的数字对应的值等于 vali 。nums2[i] [idi, …...
【C#基础】C# 常用语句讲解
序号系列文章3【C#基础】C# 数据类型总结4【C#基础】C# 变量和常量的使用5【C#基础】C# 运算符总结文章目录前言语句概念1,迭代语句1.1 for 语句1.2 foreach 语句1.3 while 语句1.4 do 语句2,选择语句2.1,if 语句2.2,else 语句2.3…...
腾讯云——负载均衡CLB
负载均衡 CLB 提供四层(TCP 协议/UDP 协议/TCP SSL 协议)和七层(HTTP 协议/HTTPS 协议)负载均衡。您可以通过 CLB 将业务流量分发到多个后端服务器上,消除单点故障并保障业务可用性。CLB 自身采用集群部署,…...
Docker 运行 Kafka 带 SASL 认证教程
Docker 运行 Kafka 带 SASL 认证教程 Docker 运行 Kafka 带 SASL 认证教程一、说明二、环境准备三、编写 Docker Compose 和 jaas文件docker-compose.yml代码说明:server_jaas.conf 四、启动服务五、验证服务六、连接kafka服务七、总结 Docker 运行 Kafka 带 SASL 认…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...
鸿蒙中用HarmonyOS SDK应用服务 HarmonyOS5开发一个医院查看报告小程序
一、开发环境准备 工具安装: 下载安装DevEco Studio 4.0(支持HarmonyOS 5)配置HarmonyOS SDK 5.0确保Node.js版本≥14 项目初始化: ohpm init harmony/hospital-report-app 二、核心功能模块实现 1. 报告列表…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一)
宇树机器人多姿态起立控制强化学习框架论文解析 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(一) 论文解读:交大&港大&上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化…...
微信小程序云开发平台MySQL的连接方式
注:微信小程序云开发平台指的是腾讯云开发 先给结论:微信小程序云开发平台的MySQL,无法通过获取数据库连接信息的方式进行连接,连接只能通过云开发的SDK连接,具体要参考官方文档: 为什么? 因为…...
零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
浪潮交换机配置track检测实现高速公路收费网络主备切换NQA
浪潮交换机track配置 项目背景高速网络拓扑网络情况分析通信线路收费网络路由 收费汇聚交换机相应配置收费汇聚track配置 项目背景 在实施省内一条高速公路时遇到的需求,本次涉及的主要是收费汇聚交换机的配置,浪潮网络设备在高速项目很少,通…...
GO协程(Goroutine)问题总结
在使用Go语言来编写代码时,遇到的一些问题总结一下 [参考文档]:https://www.topgoer.com/%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B/goroutine.html 1. main()函数默认的Goroutine 场景再现: 今天在看到这个教程的时候,在自己的电…...
【Android】Android 开发 ADB 常用指令
查看当前连接的设备 adb devices 连接设备 adb connect 设备IP 断开已连接的设备 adb disconnect 设备IP 安装应用 adb install 安装包的路径 卸载应用 adb uninstall 应用包名 查看已安装的应用包名 adb shell pm list packages 查看已安装的第三方应用包名 adb shell pm list…...
