学习总结
2021-04-12 00:03:58 65 举报
AI智能生成
针对使用到的框架和一些基础知识的总结
作者其他创作
大纲/内容
Spring
IoC DI
假设依赖链路上某个对象多次出现
第一次getBean时将对象工厂放入三级缓存
第二次getBean时回调对象工厂获取对象,并将获取到的共享对象放入二级缓存
后续每次getBean时直接从二级缓存获取对象
最后由第一次getBean的方法完成Bean的初始化,并将初始化好的对象移入一级缓存
第一次getBean时将对象工厂放入三级缓存
第二次getBean时回调对象工厂获取对象,并将获取到的共享对象放入二级缓存
后续每次getBean时直接从二级缓存获取对象
最后由第一次getBean的方法完成Bean的初始化,并将初始化好的对象移入一级缓存
ApplicationContext
extends
EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver
extends
EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory,
MessageSource, ApplicationEventPublisher, ResourcePatternResolver
ApplicationContext是BeanFactory的一个包装器实现
添加额外功能,配置解析,环境变量支持,多语和事件驱动机制
添加额外功能,配置解析,环境变量支持,多语和事件驱动机制
AOP
首先创建好原始对象并完成依赖注入之后
initializeBean阶段,在BeanPostProcessor中创建代理对象
调用时调用了代理方法,切面责任链通过递归调用和目标方法穿插执行
initializeBean阶段,在BeanPostProcessor中创建代理对象
调用时调用了代理方法,切面责任链通过递归调用和目标方法穿插执行
MVC
第一次调用首先初始化九大组件
从IoC容器获取相应的Bean去填充成员变量
doDispatch
根据请求得到Handler链路执行器
该执行器内部存储的Handler一般为Controller的某个method
该执行器内部存储的Handler一般为Controller的某个method
找到能够处理该handler的handler适配器
由该适配器处理handler
调用目标方法
-> Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs)
获得返回结果
-> Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs)
获得返回结果
选择一个返回值处理器进行处理
对于REST接口使用RequestResponseBodyAdviceChain
handler.handleReturnValue
writeWithMessageConverters
将反回结果经由消息转换器写入response
将反回结果经由消息转换器写入response
此时请求端收到返回结果
对于非Rest请求,会返回mav
processDispatchResult
处理请求结果
处理请求结果
render(mv, request, response)
-> view.render(mv.getModelInternal(), request, response)
根据视图名称找到视图进行渲染
-> view.render(mv.getModelInternal(), request, response)
根据视图名称找到视图进行渲染
Boot
启动流程和自动加载机制原理
https://www.processon.com/view/link/6075c6841e08534f371c2f0f
https://www.processon.com/view/link/6075c6841e08534f371c2f0f
Mybatis
创建会话工厂
解析配置 -> 全局配置文件和Mapper配置文件
解析存储resultMaps、parameterMaps
解析存储mappedStatements:namespace+id到MappedStatement(SQL信息)的映射
根据namespace将接口类型type与MapperProxyFactory进行绑定注册至MapperRegistry
用于后续创建代理对象时使用(JDK代理对象使用MapperProxy作为拦截器)
(接口类型非必须,存在时才注册,在于用户是否使用接口的方式执行SQL)
用于后续创建代理对象时使用(JDK代理对象使用MapperProxy作为拦截器)
(接口类型非必须,存在时才注册,在于用户是否使用接口的方式执行SQL)
存在接口类型的情况则通过MapperAnnotationBuilder解析接口上的注解
解析完毕返回Configuration创建DefaultSqlSessionFactory
创建会话
拿到TransactionFactory去创建一个TX
注意:TXManager为全局配置文件的必配属性,由该配置生成TXFactory
注意:TXManager为全局配置文件的必配属性,由该配置生成TXFactory
根据执行器类型和TX去创建一个执行器
若全局开启二级缓存则用CachingExecutor包装原有的执行器
全局开启代表会使用CachingExecutor包装实际的执行器
使Executor拥有了和二级缓存交互的能力
使Executor拥有了和二级缓存交互的能力
Mapper中使用Cache标签代表针对Mapper生成一个二级缓存
此时执行器还没有和二级缓存交互的能力
只有全局和Mapper同时开启时二级缓存才生效
此时执行器还没有和二级缓存交互的能力
只有全局和Mapper同时开启时二级缓存才生效
插件链通过JDK动态代理层层包装执行器Executor
返回DefaultSqlSession
获取代理对象
通过MapperRegistry获取MapperProxyFactory创建代理对象
MapperProxyFactory由创建会话工厂的第四步完成注入
MapperProxy作为方法拦截器
MapperProxyFactory由创建会话工厂的第四步完成注入
MapperProxy作为方法拦截器
MapperProxy包含:
SqlSession用于执行SQL;
mapperInterface接口类型用于和方法名称共同组成唯一ID找到SQL信息;
methodCache用于缓存提高性能。
SqlSession用于执行SQL;
mapperInterface接口类型用于和方法名称共同组成唯一ID找到SQL信息;
methodCache用于缓存提高性能。
SQL执行
第一次调用时构建MapperMethodInvoker并缓存
然后委托内部的MapperMethod执行
然后委托内部的MapperMethod执行
在MapperMethod执行方法内会解析方法参数
拿到保存的SQL的ID后交给SqlSession执行
此时相当于体现了接口的功能,即不需要手动通过设置ID和参数去调用SqlSession
拿到保存的SQL的ID后交给SqlSession执行
此时相当于体现了接口的功能,即不需要手动通过设置ID和参数去调用SqlSession
SqlSession根据Configuration拿到MS交由执行器执行语句
执行器执行SQL
假设此时为Select
假设此时为Select
执行Executor的插件逻辑
执行CachingExecutor逻辑
创建CacheKey
组成:Id、Offset、Limit、Sql、value、环境ID
注意:通过Hash提高了比较性能
注意:通过Hash提高了比较性能
当Mapper.xml中配置了Cache标签则走二级缓存逻辑
注意:因为二级缓存跨会话,所以需要与事务绑定
二级缓存写入时首先会放入TransactionalCache的entriesToAddOnCommit
当事务提交时才会写入实际缓存
当事务提交时才会写入实际缓存
执行器执行SQL
查询本地缓存,本地缓存与执行器Executor1对1绑定,执行器与Session1对1绑定
实际执行
创建StatementHandler,会被插件包装
创建ParameterHandler,会被插件包装
创建ResultSetHandler,会被插件包装
根据StatementHandler创建Statement
通过ParameterHandler对Statement设置参数
调用ps.execute();执行查询
通过ResultSetHandler处理结果集
调用ps.execute();执行查询
本地缓存为STATEMENT级别则清除缓存
Vertx
线程模型原理
https://www.processon.com/view/link/603c8acd07912913b4f26903
https://www.processon.com/view/link/603c8acd07912913b4f26903
Netty
详细总结
https://www.processon.com/view/link/602cbfa8e0b34d208a81c015#map
https://www.processon.com/view/link/602cbfa8e0b34d208a81c015#map
服务端启动
入口:bind()
initAndRegister()
new NioServerSocketChannel()
openServerSocketChannel 创建 JDK ServerSocketChannel
id
unsafe -> NioMessageUnsafe
pipeline -> DefaultChannelPipeline
HeadContext
TailContext
SelectionKey.OP_ACCEPT 保存感兴趣的事件
ChannelConfig
init(channel)
设置各种参数
pipeline添加ServerBootstrapAcceptor
用于处理新连接接入
bossGroup.next().register(channel)
根据Chooser选择下一个NioEventLoop交给channel
根据Chooser选择下一个NioEventLoop交给channel
AbstractChannel.register(eventLoop, p)
NioEventLoop.execute
第一次执行
startThread()
startThread()
executor.execute
由ThreadPerTaskExecutor创建一个线程执行任务
SingleThreadEventExecutor.this.run()
此时NioEventLoop正式启动,开始无限循环
此时NioEventLoop正式启动,开始无限循环
addTask(task)
unsafe.register
-> AbstractChannel.this.eventLoop = eventLoop
此时保存Channel和EventLoop的关系
-> AbstractChannel.this.eventLoop = eventLoop
此时保存Channel和EventLoop的关系
register0(p)
doRegister()
NioServerSocketChannel内部的JDK Channel
注册至NioEventLoop内部的JDK Selector
注意:此时尚未监听任何感兴趣的事件
注册至NioEventLoop内部的JDK Selector
注意:此时尚未监听任何感兴趣的事件
pipeline.invokeHandlerAddedIfNeeded()
ChannelInitializer.handlerAdded
-> initChannel(ctx)
-> initChannel(ctx)
pipeline.fireChannelRegistered()
doBind0(channel)
channel.bind()
-> pipeline.bind
-> tail.bind
bind事件从尾到头传播
-> pipeline.bind
-> tail.bind
bind事件从尾到头传播
HeadContext.bind
-> unsafe.bind
-> doBind
此时JDK Channel与端口进行绑定
-> unsafe.bind
-> doBind
此时JDK Channel与端口进行绑定
pipeline.fireChannelActive()
通道激活传播事件
通道激活传播事件
AbstractChannelHandlerContext.invokeChannelActive(head)
HeadContext.channelActive
当传播结束时,调用readIfIsAutoRead();
channel.read()
-> tail.read()
-> HeadContext.read()
-> tail.read()
-> HeadContext.read()
unsafe.beginRead()
Channel.doBeginRead
selectionKey.interestOps(interestOps | readInterestOp)
开始对之前保存的感兴趣事件进行监听,此时为Accept事件
开始对之前保存的感兴趣事件进行监听,此时为Accept事件
注意:
对于Inbound事件,从头部往尾部传播;
对于Outbound事件,从尾部往前传播;
Inbound为被动事件
OutBound为主动事件
对于Inbound事件,从头部往尾部传播;
对于Outbound事件,从尾部往前传播;
Inbound为被动事件
OutBound为主动事件
NioEventLoop
伴随NioEventLoopGroup创建
EventExecutor[] children
chooser
可以认为,Group是一个拥有固定核心线程数的一个线程池
NioEventLoop为内部的一个核心线程
一个Group下的NioEventLoop共用一个ThreadPerTaskExecutor进行线程创建
NioEventLoop执行任务时,会委托内部的Executor执行,此时会创建一个线程,开启无限循环
NioEventLoop内部会包含这个创建好的线程,用来判断提交任务的线程是否为EventLoop线程
NioEventLoop为内部的一个核心线程
一个Group下的NioEventLoop共用一个ThreadPerTaskExecutor进行线程创建
NioEventLoop执行任务时,会委托内部的Executor执行,此时会创建一个线程,开启无限循环
NioEventLoop内部会包含这个创建好的线程,用来判断提交任务的线程是否为EventLoop线程
启动
NioEventLoop.execute
第一次启动时此时内部保存的线程为null,启动NioEventLoop
doStartThread()
executor.execute
由ThreadPerTaskExecutor分配一个线程执行启动逻辑
由ThreadPerTaskExecutor分配一个线程执行启动逻辑
this.thread = Thread.currentThread();
NioEventLoop内部保存这个创建好的线程
NioEventLoop内部保存这个创建好的线程
SingleThreadEventExecutor.this.run()
-> NioEventLoop.run()
正式启动NioEventLoop,即无限循环
-> NioEventLoop.run()
正式启动NioEventLoop,即无限循环
执行逻辑
select
阻塞式Select,当有穿插任务时,会被唤醒
解决空轮训Bug
本应该阻塞实际未发生阻塞
并且select次数超过512次
触发rebuildSelector,将旧的selector上注册的channel迁移至新的selector上
并且select次数超过512次
触发rebuildSelector,将旧的selector上注册的channel迁移至新的selector上
processSelectedKeys
处理IO事件
runAllTasks
默认处理IO事件与任务的事件比为1比1
即任务并不一定能够一次处理完毕
即任务并不一定能够一次处理完毕
循环获取定时任务中应该执行的任务,将其放入taskQueue
在deadline之前不断取出task执行
afterRunningAllTasks
当执行过任务时,执行尾部任务
新连接接入
unsafe.read()
不断的accept连接
创建NioSocketChannel
-> 放入readBuf这个List中
创建NioSocketChannel
-> 放入readBuf这个List中
NioSocketChannel同样保存类似于NioServerSocketChannel的信息
循环调用pipeline.fireChannelRead(NioSocketChannel)
从Head节点向后传播
ServerBootstrapAcceptor.channelRead
处理新连接
处理新连接
childGroup.register(child)
给Channel分配一个Reactor线程
给Channel分配一个Reactor线程
unsafe().register
register0
doRegister
将Netty Channel内部的JDK Channel与Reactor内部的Selector绑定
注意:此时未监听任何事件
将Netty Channel内部的JDK Channel与Reactor内部的Selector绑定
注意:此时未监听任何事件
pipeline.invokeHandlerAddedIfNeeded()
触发Child的ChannelInitializer的initChannel方法
触发Child的ChannelInitializer的initChannel方法
pipeline.fireChannelRegistered()
pipeline.fireChannelActive()
注意:
对于SocketChannel来说,accept后已经处于激活状态,所以此时直接触发active事件
Server的Channel需要bind才会处于激活状态
注意:
对于SocketChannel来说,accept后已经处于激活状态,所以此时直接触发active事件
Server的Channel需要bind才会处于激活状态
Active从Head节点开始传播
Head节点传播结束后,调用readIfIsAutoRead()
Head节点传播结束后,调用readIfIsAutoRead()
unsafe.beginRead()
设置感兴趣的事件为SelectionKey.OP_READ
设置感兴趣的事件为SelectionKey.OP_READ
pipeline.fireChannelReadComplete()
IO
BIO
NIO
MappedByteBuffer用于内存映射文件
所能打开的最大连接数
FD巨增的情况
消息传递方式
AIO
对比
MySql
组成
连接器,管理连接,验证身份和权限
解析器,词法语法分析,生成解析树
预处理器,检查,表,字段是否存在
优化器,SQL语句优化,生成执行计划,通过explain可以查看
执行引擎,执行执行计划,返回数据
常见存储引擎,提供读写接口
innodb
myisam
InnoDB
内存结构
Buffer Pool
包含Change buffer
包含Change buffer
按页读取放入缓冲池,页默认大小16K
预读机制,加载更多的页
预读机制,加载更多的页
修改数据写入缓冲池,此时数据不一致为脏页
由后台线程进行刷脏
由后台线程进行刷脏
Log Buffer(redo log)
重做日志,属于物理日志,记录数据页上的操作
顺序写,当写满时触发buffer pool刷脏
用于崩溃恢复,如buffer pool尚未刷脏,即满足D(持久性)需求
顺序写,当写满时触发buffer pool刷脏
用于崩溃恢复,如buffer pool尚未刷脏,即满足D(持久性)需求
log buffer写入时机
0 延迟写,log buffer每秒1次写入file并flush至磁盘
1 默认,实时写,实时刷,每次事务提交写入file并flush至磁盘
2 实时写,延时刷,实时写入file,每隔1秒flush
undo log
回滚日志,属于逻辑日志,记录事务发生前的状态
记录反向操作,insert记录delete,update记录原来的值
为满足A(原子性)需求
记录反向操作,insert记录delete,update记录原来的值
为满足A(原子性)需求
磁盘结构
系统表空间
数据字典,undo logs
双写缓冲区
由于操作系统page大小为4K,innoDB默认为16K
需4次写入,可能导致部分写失效
故写入磁盘page时先顺序写一份page的副本
由于操作系统page大小为4K,innoDB默认为16K
需4次写入,可能导致部分写失效
故写入磁盘page时先顺序写一份page的副本
独占表空间
ibd,存放表的索引和数据
共享表空间,临时表空间
redo log
bin log
由Server层提供
由Server层提供
属于逻辑日志,记录了所有的操作,DDL,DML
用于主从复制和数据恢复
文件内容可追加,没有大小限制
用于主从复制和数据恢复
文件内容可追加,没有大小限制
更新语句执行
客户端发送更新语句至Server层
执行引擎执行更新语句后
调用存储引擎接口进行更新后数据的存储
调用存储引擎接口进行更新后数据的存储
InnoDB将修改结果写入内存中
InnoDB记录undo log和redo log,此时redo log状态设为prepare
返回执行器,代表此时可以发生提交
执行器记录bin log日志
执行器调用接口提交事务
InnoDB将redo log设为commit状态
语句执行完毕
索引
数据结构
多路平衡查找树(Balanced Tree)
解决了平衡二叉树节点对Page页利用不足的情况
InnoDB按页读取数据
所以通过B Tree在一个节点(16K)上存储足够多的索引数据
B Tree其实就是平衡多叉树,节点空间利用足够多,减少磁盘IO
频繁更新的列上建索引会导致树不断的分裂和合并
InnoDB按页读取数据
所以通过B Tree在一个节点(16K)上存储足够多的索引数据
B Tree其实就是平衡多叉树,节点空间利用足够多,减少磁盘IO
频繁更新的列上建索引会导致树不断的分裂和合并
B+树
节点内关键字和路数相等
关键字存储的磁盘位置为关键字到下一个关键字的左闭右开区间
磁盘位置引用:[关键字,下一个关键字)
关键字存储的磁盘位置为关键字到下一个关键字的左闭右开区间
磁盘位置引用:[关键字,下一个关键字)
根节点和枝节点不存储数据,只有叶子节点存储数据
叶子节点之间构成双向链表
叶子节点之间构成双向链表
优势:非叶子节点不再存储数据,存储更多关键字,树深更低,减少磁盘IO
范围查找优势,通过底层双向链表遍历
IO性能稳定,都需要从子节点获取数据
范围查找优势,通过底层双向链表遍历
IO性能稳定,都需要从子节点获取数据
存储引擎存储方式
myisam
数据存在.MYD
索引存在.MYI
所有的索引叶子节点都为数据的物理磁盘地址,从MYD查找数据
所有的索引叶子节点都为数据的物理磁盘地址,从MYD查找数据
innodb
聚集(簇)索引 - 索引顺序与物理磁盘顺序一致
若有主键索引,则选择主键索引作为聚集索引,叶子节点存放数据
其他索引为二级索引,叶子节点存放主键值,需要回表
其他索引为二级索引,叶子节点存放主键值,需要回表
二级索引查询,首先找到主键值,在主键索引查询
叶子节点未存放物理地址,因为物理地址可能发生变化
叶子节点未存放物理地址,因为物理地址可能发生变化
若没有主键索引,则选择第一个不包含null的唯一索引作为主键索引,选其为聚集索引
若没有主键索引也没有非空唯一索引
则innoDB生成一个默认6字节长的_rowid作为隐藏的聚集索引,随着插入单调递增
则innoDB生成一个默认6字节长的_rowid作为隐藏的聚集索引,随着插入单调递增
索引使用原则
离散度
联合索引最左匹配原则
覆盖索引
当数据列所需的字段在二级索引中就能获得,则不需要回表
这种使用索引的方式称为覆盖索引
注意:当查询未使用索引时,但是优化器发现使用索引树查询列数据更快时
仍然会发生覆盖索引
extra:'Using index' 覆盖索引
这种使用索引的方式称为覆盖索引
注意:当查询未使用索引时,但是优化器发现使用索引树查询列数据更快时
仍然会发生覆盖索引
extra:'Using index' 覆盖索引
索引条件下推(ICP)
针对二级索引,未使用索引仍由索引过滤数据称为索引条件下推,默认开启
如 %xxx 未使用索引(未根据索引匹配数据,但有可能通过索引树查数据,根据列字段判断)
发生条件下推时由二级索引过滤数据然后回表,返回Server层的数据在某种情况下可以骤减
相当于在存储引擎层完成了数据过滤
extra:Using index condition 发生索引条件下推,即由索引过滤数据
extra:Using where 返回的数据不满足全部条件,由Server层过滤
如 %xxx 未使用索引(未根据索引匹配数据,但有可能通过索引树查数据,根据列字段判断)
发生条件下推时由二级索引过滤数据然后回表,返回Server层的数据在某种情况下可以骤减
相当于在存储引擎层完成了数据过滤
extra:Using index condition 发生索引条件下推,即由索引过滤数据
extra:Using where 返回的数据不满足全部条件,由Server层过滤
全表扫描索引
根据列字段全表扫描发生在索引树的情况,我们需要关注rows字段
这种情况为使用了索引但是全表扫描了索引
这种情况为使用了索引但是全表扫描了索引
常用字段说明
type
数据匹配类型
possible_keys
可能用到的索引
key
实际使用到的索引
rows
扫描行数
extra
'Using index' 覆盖索引,无需回表,直接使用索引树
Using index condition 发生索引条件下推,即由索引过滤数据
Using where 返回的数据不满足全部条件,由Server层过滤
事务
特性
atomicity
操作共同失败成功
undolog保证
undolog保证
consistency
分为数据库一致性和用户自定义一致性
数据库一致性:由其他三个属性保证,保证数据的合法状态
用户自定义一致性:
能量守恒,数据不会凭空多/少,或非法如余额<0的状态(通常在代码中控制)
数据库一致性:由其他三个属性保证,保证数据的合法状态
用户自定义一致性:
能量守恒,数据不会凭空多/少,或非法如余额<0的状态(通常在代码中控制)
isolation
并发干扰
注意:事务的原子性和并发编程中原子性的区别
注意:事务的原子性和并发编程中原子性的区别
durability
只要事务提交,结果就为永久性的
redolog和双写缓冲保证
redolog和双写缓冲保证
并发的三大问题
即读一致性问题
即读一致性问题
脏读:一个事务读到另一个事物未提交的数据
此时第二个事务回滚时会导致数据不一致
此时第二个事务回滚时会导致数据不一致
不可重复读:一个事务内读到另一个事务提交的修改后的数据
幻读:一个事务内的范围查询读到另一个事务提交插入的数据
注意:幻读特指新插入的行
注意:幻读特指新插入的行
读不一致:未发生更新数据时事务内两次读到的数据不一致
解决方案,由数据库的隔离性解决
读一致性解决方案
LBCC
加锁,不允许并发读写,即一个事务读时不允许其他事务写
MVCC
基于数据版本
能看到:
第一次查询前已提交的事务的修改
本事务的修改
不能看到:
比当前事务id大的事务,即当前事务后创建的事务
未提交的事务
第一次查询前已提交的事务的修改
本事务的修改
不能看到:
比当前事务id大的事务,即当前事务后创建的事务
未提交的事务
隐藏字段
DB_TRX_ID
当前事务ID,创建版本号
DB_ROLL_PTR
回滚指针,删除版本号
Read View
m_ids当前系统活跃(未提交)事务ids
min_trx_id 最小m_ids
max_trx_id 系统分配的下一事务id
creator_trx_id 生成RV的事务id
可见性算法
trxid = c-trx-id,当前事务修改,可见
id <min_id,生成RV时已提交的数据,可见
id>max_id,生成RV后提交的数据,不可见
min<id<max,在活跃id中则不可见,否则可见
由最新的版本往前查找,若可见则停止
RR第一次快照读时建立RV
RC每次快照读时均建立RV
RC每次快照读时均建立RV
锁
共享锁/读锁
排它锁/写锁
增删改会自动加互斥锁
锁的释放
当事务提交时释放锁
意向共享/排他锁
用于表示有数据行已发生加锁行为
便于加表锁时的判断,不需要扫描行是否加锁
用于提交加锁效率
便于加表锁时的判断,不需要扫描行是否加锁
用于提交加锁效率
行锁原理
对于主键索引和二级索引来说,锁的是主键索引
对于全表扫描来说,锁的是整个表
对于全表扫描来说,锁的是整个表
Record记录锁
根据索引精确匹配时只会锁住一行
Gap间隙锁
等值/范围查询未命中数据行时,使用间隙锁
间隙锁用于阻塞insert,相同的间隙锁之间不冲突
锁区间
Next-Key临键锁
范围查询包含记录和区间时
间隙锁+记录锁:(x, y]
间隙锁+记录锁:(x, y]
对于间隙,阻塞插入
对于记录,阻塞获取锁,即无法修改或带锁查询
对于记录,阻塞获取锁,即无法修改或带锁查询
锁住当前区间和下一个左开右闭区间
阻塞了insert操作,即不会出现幻读
阻塞了insert操作,即不会出现幻读
innoDB隔离级别的实现
RU 不加锁
序列化 Select隐式加共享锁,与修改互斥
RR
普通Select使用快照读,第一次生成RV,基于MVCC实现
加锁Select和更新使用当前读,底层使用记录锁,间隙锁,邻键锁
RC
普通Select使用快照读,每次生成RV
只使用记录锁,没有Gap锁
并不会阻塞insert操作,无法解决幻读问题,修改时的受影响行数和看到的不一致
并不会阻塞insert操作,无法解决幻读问题,修改时的受影响行数和看到的不一致
RR和RC区别
RR间隙锁导致锁定范围增大
条件列未使用索引时,RR锁表,RC锁行
测试结果:
在RC下同样使用非索引字段会互斥,产生锁表效果,但一方使用非索引字段,一方使用索引字段,不会互斥(若同一行数据仍然互斥)
表现结果为:锁非索引字段和锁行记录,不会阻塞索引字段上的非当前行加锁,使用索引字段加锁会导致非索引字段全表锁定
RR任何时候未匹配索引,会导致锁表,即使一方使用非索引字段,一方使用索引字段
测试结果:
在RC下同样使用非索引字段会互斥,产生锁表效果,但一方使用非索引字段,一方使用索引字段,不会互斥(若同一行数据仍然互斥)
表现结果为:锁非索引字段和锁行记录,不会阻塞索引字段上的非当前行加锁,使用索引字段加锁会导致非索引字段全表锁定
RR任何时候未匹配索引,会导致锁表,即使一方使用非索引字段,一方使用索引字段
RC半一致性读增加update效率
优化
连接
增加可用连接数
及时释放不活动的连接,防止客户端假死导致连接未释放
客户端层面对连接复用,使用连接池
设置合适的连接池大小
设置合适的连接池大小
缓存
报表数据缓存
数据缓存,减少数据库复杂查询
主从,读写分离
分库分表
慢查询优化
开启慢查询,设置慢查询参数
explain分析
id不同时按id顺序逆序分析,id最大为最里的子查询
id相同为关联查询,正序分析
小表驱动大表,先执行查询结果少的表
小表驱动大表,先执行查询结果少的表
select type
SIMPLE
简单查询,不包含子查询
简单查询,不包含子查询
PRIMARY 主查询,外层查询
SUBQUERY 子查询
DERIVED 衍生查询,用到临时表
UNION 联合查询
SUBQUERY 子查询
DERIVED 衍生查询,用到临时表
UNION 联合查询
type
index
full index scan
查询全部索引中的数据,特殊的全表扫描
查询全部索引中的数据,特殊的全表扫描
all
full table scan
全表扫描
全表扫描
possible keys 可能用到的索引
key 实际用到的索引
key 实际用到的索引
key_len 索引长度
rows 预估扫描行数
filters
存储引擎返回的有效数据比例
当比例很低时,代表返回了过多的数据需要在Server层过滤
当比例很低时,代表返回了过多的数据需要在Server层过滤
extra
using index 覆盖索引无需回表
using where 需要Server层过滤数据
using index condition 索引条件下推
using filesort 无法使用索引排序,使用了额外的排序,需要优化
using temporary 使用了临时表
字段定义
恰当的整数类型
固定长度使用char,varchar需要一个字节记录长度
非空,使用特殊值代替null
不要使用外键,触发器,存储过程,视图
大文件存储至文件服务器,数据库只存url
表拆分或字段冗余
利用事务的超卖解决方案
假设两个并发事务共同开启
两个事务共同查询发现余额为1,可消费
假设第二个事务此时发生更新,那么由于锁定机制第一个事务的update阻塞
第二个事务更新后发现库存减少到0,那么提交事务
第一个事务在第二个事务提交后完成更新操作,之后继续读操作,发现库存减少到-1,回滚
两个事务共同查询发现余额为1,可消费
假设第二个事务此时发生更新,那么由于锁定机制第一个事务的update阻塞
第二个事务更新后发现库存减少到0,那么提交事务
第一个事务在第二个事务提交后完成更新操作,之后继续读操作,发现库存减少到-1,回滚
Redis
数据类型
String
存储形式
redisObject是对五大数据类型的抽象
type为对外类型,encoding为编码类型
指向实际的存储对象,此时为string
type为对外类型,encoding为编码类型
指向实际的存储对象,此时为string
SDS
Simple Dynamic String
Simple Dynamic String
C语言本身没有字符串
通过字符数组存储字符,需要提前预先分配足够的空间
本身是个字符数组,获取长度需要遍历
C函数库操作字符串的方式通过\0结尾,存储一些二进制文件可能不安全
通过字符数组存储字符,需要提前预先分配足够的空间
本身是个字符数组,获取长度需要遍历
C函数库操作字符串的方式通过\0结尾,存储一些二进制文件可能不安全
SDS是一个结构体,可以近似认为一个对象
提供了自动扩容,记录字符串长度的能力
通过预分配和惰性释放防止每次重新分配内存
通过len长度属性判断字符串结尾,二进制安全
提供了自动扩容,记录字符串长度的能力
通过预分配和惰性释放防止每次重新分配内存
通过len长度属性判断字符串结尾,二进制安全
编码类型
int 存储8字节长整型,当长度超出时转为embstr
embstr 存储小于44字节的字符串
设置时大于44字节时转为raw
或每次修改时转为raw
设置时大于44字节时转为raw
或每次修改时转为raw
一次连续内存分配,分配RedisObject和SDS
释放只需要一次释放
释放只需要一次释放
只读,每次修改时转为raw
raw 存储大于44字节的字符串
两次内存分配,分别分配
使用场景
缓存热点,报表数据
分布式session
基于setnx的分布式锁
注意:并不推介使用这种方式,无法实现可靠稳定的锁
推介使用lua脚本的方式实现
注意:并不推介使用这种方式,无法实现可靠稳定的锁
推介使用lua脚本的方式实现
全局ID生成器 incrby
计数器 incr
限流 如密码输错N次不允许再次输入
限流 如密码输错N次不允许再次输入
Hash / HSet
String类型组成的Map
编码类型
ziplist
特殊的双向链表,本质为一个数组
不存储指向pre和post的指针
取代存储上个节点长度和当前节点长度
时间换空间
不存储指向pre和post的指针
取代存储上个节点长度和当前节点长度
时间换空间
当field < 512
且所有k-v字符串长度和 < 64字节
即字段个数小且字段值小的情况下使用ziplist
且所有k-v字符串长度和 < 64字节
即字段个数小且字段值小的情况下使用ziplist
hashtable
数组+链表
定义了两个hash表,用于扩容使用
类似的有Netty使用的2个hash表
chm内部也会多创建一个hash表
定义了两个hash表,用于扩容使用
类似的有Netty使用的2个hash表
chm内部也会多创建一个hash表
使用场景
存储对象,如JSON对象本质就是一个map
String能做的都可以做
购物车
key 用户ID
field 商品id
value 商品数量
hincr/hdecr 增加减少购买数量
hgetall 全选
hlen 商品数量
List
链表,按照String元素插入排序
编码类型:quicklist
双向链表+数组
quicklist中的每个quickNode存储一个ziplist
quicklist中的每个quickNode存储一个ziplist
使用场景
存储有序内容
分布式阻塞队列/分布式队列
分布式锁排队
Set
String类型组成的无序集合
本质是个隐藏value的HashMap
本质是个隐藏value的HashMap
编码类型
intset
当元素全为整形时
hashtable
不是整形/元素个数超出512
使用场景
spop 抽奖,随机获取元素
sadd 点赞
srem 取消点赞
sismember 是否已经点赞
smembers 获取所有点赞用户
scard 点赞数
srem 取消点赞
sismember 是否已经点赞
smembers 获取所有点赞用户
scard 点赞数
sadd 商品标签
sdiff 差集 可能认识的人
sinter 交集 共同关注
sunion 并集
sinter 交集 共同关注
sunion 并集
ZSet / Sorted set
String类型元素组成的有序集合
每个元素有一个score,由小到大排序
每个元素有一个score,由小到大排序
编码类型
ziplist
按照score递增排序,插入时需要移动之后的数据
skiplist
元素个数大于等于128个
或任一member字符串长度大于等于64字节
或任一member字符串长度大于等于64字节
通过设置不同的节点高度,来减少查找数量
首先从最高的节点找,当发现查找节点小于某个节点是回退去下一层节点找
首先从最高的节点找,当发现查找节点小于某个节点是回退去下一层节点找
使用场景
实现任务权重队列
排行榜
点击数
获取今天点击量最高的数据
BitMap
位图
HyperLogLog
不精确的数据统计,内存占用非常小
Geo
二维记录
Stream
类似于kafka的消息队列
其他使用场景
基于发布订阅
服务注册与发现
配置中心
分布式锁的唤醒
持久化机制
RDB
redis database
redis database
定时触发
shutdown触发
save 当前保存,阻塞redis处理线程
bgsave 后台保存,fork子进程处理数据生成rdb
由于OS的copy-on-write机制,两个进程使用同一份数据
当父进程修改数据时会重新创建一份数据,所以子进程相当于拿了一个快照数据
bgsave 后台保存,fork子进程处理数据生成rdb
由于OS的copy-on-write机制,两个进程使用同一份数据
当父进程修改数据时会重新创建一份数据,所以子进程相当于拿了一个快照数据
redis down掉会丢失最后一次快照之后的数据
主从复制时触发
AOF
append only file
append only file
通过日志的顺序写记录每个写操作,先写入磁盘缓存
持久化策略
磁盘缓存
磁盘缓存
从不fsync,由操作系统刷盘
每次写入刷盘
每秒刷盘 默认
通过重写机制防止文件过大
读取现有键值对,保留恢复当前数据的最小指令集
读取现有键值对,保留恢复当前数据的最小指令集
百分比参数 100
当文件大小为上次重写后AOF文件的2倍进行重写
当文件大小为上次重写后AOF文件的2倍进行重写
最小文件参数 64M
手动触发:BGREWRITEAOF
比较
启动优先级:AOF > RDB
体积: AOF > RDB
恢复速度:RDB更快
数据安全:RDB丢失最后一次快照后的数据,AOF由策略决定
RDB每次进行全量同步,影响磁盘IO性能
常见问题
数据一致性
实时性不强的情况下,可以使用定时任务/过期时间令缓存数据失效
从而达到最终一致性
从而达到最终一致性
更新缓存容易导致数据不一致,所以一般使用删除缓存
更新数据库,删除缓存,通过重试令这次删除成功
先删除缓存,后更新数据库
此时需要对数据库访问线程加锁,防止更新数据库时有别的线程重置了缓存
即当发现需要更新数据时,加锁,删缓存,更新数据库
此时需要对数据库访问线程加锁,防止更新数据库时有别的线程重置了缓存
即当发现需要更新数据时,加锁,删缓存,更新数据库
缓存三大问题
缓存雪崩
加锁或使用队列,对同一个key只有一条数据访问数据库
定时预更新
添加随机数防止同时失效
永不过期
缓存穿透
缓存空数据或特殊字符,设置较短的过期时间
当恶意请求,每次使用不同的ID访问,上面这种方式失效
使用布隆过滤器,针对假阳性的数据进行特殊缓存
使用布隆过滤器,针对假阳性的数据进行特殊缓存
缓存击穿
特殊的缓存雪崩,对操作数据库的线程加锁即可
海量数据查询某一固定前缀的key
scan模糊查询代替keys指令
pipeline
批量发送命令
主从同步原理
全同步
slave发送sync命令至master
master启动后台进程,保存RDB文件
master缓存接收到的后续写命令
master完成写文件后将文件发送给slave
使用新的rdb替换旧的rdb,进行数据恢复
master将增量写命令发送至slave进行回放
master启动后台进程,保存RDB文件
master缓存接收到的后续写命令
master完成写文件后将文件发送给slave
使用新的rdb替换旧的rdb,进行数据恢复
master将增量写命令发送至slave进行回放
增量同步
master判断接受到的命令是否为写命令,即是否需要同步
将操作记录追加至AOF中
发送数据至slave
将操作记录追加至AOF中
发送数据至slave
一致性hash
hash环倾斜,导致数据分布不均匀
虚拟节点
通过slot的分配方式
JVM
ClassLoader
默认委派关系
Bootstrap ClassLoader
-> Extendsion ClassLoader
-> Application ClassLoader
-> Customer ClassLoader
-> Extendsion ClassLoader
-> Application ClassLoader
-> Customer ClassLoader
loadClass(String name)
this.findLoadedClass(name)
-> parent.loadClass(name, false)
-> this.findClass(name)(此时一般会调用defineClass根据字节码生成Class对象)
-> resolveClass(c) (是否link,默认false)
-> parent.loadClass(name, false)
-> this.findClass(name)(此时一般会调用defineClass根据字节码生成Class对象)
-> resolveClass(c) (是否link,默认false)
Class.forName(String)
-> forName0(className, true, ClassLoader.getClassLoader(caller), caller)
注意:这里的ture,代表初始化
-> forName0(className, true, ClassLoader.getClassLoader(caller), caller)
注意:这里的ture,代表初始化
初始化时才会执行静态代码块
Verify 是用来校验加载进来的class文件是否符合class文件标准,如果不符合直接就会被拒绝了;
Prepare 是将class文件静态变量赋默认值而不是初始值,例如static int i =8;这个步骤并不是将i赋值为8,而是赋值为默认值0;
Resolve 是把class文件常量池中用到的符号引用转换成直接内存地址,可以访问到的内容;
内存结构
运行时数据区
线程共享区
方法区
已被虚拟机加载的类信息
常量池
静态变量
即时编译后的代码
常量池
静态变量
即时编译后的代码
java8之前处于永久代
java8之后处于元空间,堆外内存
堆
存储实例对象
线程独占区
虚拟机栈
存放方法运行时所需的数据,成为栈帧
栈帧 = 局部变量表+操作数栈
局部变量表包含本地变量
栈帧 = 局部变量表+操作数栈
局部变量表包含本地变量
注意:逃逸分析作用域在方法内的小对象会进行栈上分配,
并进行标量替换(将对象字段作为局部变量分配)
并进行标量替换(将对象字段作为局部变量分配)
本地方法栈
服务native方法
程序计数器
记录当前线程所执行到的字节码行号
执行引擎
jit即时编译器
将热点代码编译为本地平台相关的机器码
gc垃圾回收器
用于申请和回收垃圾
本地库接口
本地方法区
内存划分
非堆
堆
old
young
eden
s0
s1
引用类型
强引用
SoftReference
WeakReference
PhantomReference
弱引用和虚引用都不会影响对象的回收
虚引用用在直接内存的回收
内存映射文件,当对象回收时,收到通知并清理直接内存
内存映射文件,当对象回收时,收到通知并清理直接内存
对象生命周期
GC
回收时机
eden/survivor空间不足
老年代空间不足
方法区空间不足
System.gc()通知
垃圾回收算法
复制(Copy)
将内存划为相等的两块区域,每次只使用其中一块
直接将存活的对象复制到另一块区域,将使用过的内存区域一次清除
空间利用率低
适合存活率低的场景,copy数量少,效率高
分配内存只需要直接移动堆顶指针,按序分配
若不按1比1分配空间,需要额外空间担保
适合存活率低的场景,copy数量少,效率高
分配内存只需要直接移动堆顶指针,按序分配
若不按1比1分配空间,需要额外空间担保
标记-清除(Mark-Sweep)
标记:标记存活对象
清除:清除未标记的对象,释放出对应的内存空间
产生大量内存碎片
标记-整理(Mark-Compact)
标记所有存活对象
让所有存活对象向一端移动,清理掉边界外的内存
分代收集
Young区
使用复制算法,大部分对象朝生夕死,由Old区作分配担保
Old区
使用标记清除或标记整理算法,old区对象存活时间长,复制算法效率低下
标记算法
引用计数
GC Root链路
双色标记
三色标记
垃圾收集器
垃圾回收算法的具体实现
Serial
复制算法,单线程收集,新生代
Serial Old
标记-整理算法,单线程收集,老年代
ParNew
Serial的多线程版本,新生代
Parallel Scavenge
类似ParNew,可以控制吞吐量和停顿时间
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间
-XX:GCTimeRatio直接设置吞吐量的大小。
-XX:MaxGCPauseMillis控制最大的垃圾收集停顿时间
-XX:GCTimeRatio直接设置吞吐量的大小。
Parallel Old
Parallel Scavenge的老年代版本,标记-整理算法
CMS
Concurrent Mark Sweep
Concurrent Mark Sweep
三色标记,减少STW时间
由于并发清除,使用了标记清除算法
concurrent mode failure导致其退化为serial old
老年代空间不足,只能暂停用户线程进行清理
由于并发清除,使用了标记清除算法
concurrent mode failure导致其退化为serial old
老年代空间不足,只能暂停用户线程进行清理
初始标记,GC ROOT直接可达对象
并发标记,由这些灰色节点出发标记
重新标记,对突变导致变回灰色的节点重新标记
并发清理
清除不可达对象,注意finalize方法
G1
Garbage First
Garbage First
内存布局基于Region,新老不再物理隔离,分代收集
H:当对象大小等于Region一半时认为大对象,放入这个区
region之间使用复制算法,分配内存通过指针碰撞
H:当对象大小等于Region一半时认为大对象,放入这个区
region之间使用复制算法,分配内存通过指针碰撞
G1使用了三色标记算法,标记整理算法
-XX: G1HeapReginSize 设置Region大小
-XX:MaxGCPauseMillis 最大停顿时间
-XX:ParallelGCThread 并行GC工作的线程数
-XX:ConcGCThreads 并发标记的线程数
-XX:InitiatingHeapOcccupancyPercent
默认45%,代表GC堆占用达到多少的时候开始垃圾收集
-XX:MaxGCPauseMillis 最大停顿时间
-XX:ParallelGCThread 并行GC工作的线程数
-XX:ConcGCThreads 并发标记的线程数
-XX:InitiatingHeapOcccupancyPercent
默认45%,代表GC堆占用达到多少的时候开始垃圾收集
术语
Stop The World
停顿时间,垃圾收集器收集过程中用户线程暂停时间
吞吐量
运行用户代码时间 / (运行用户代码+垃圾收集时间)
GC Root
方法区中的常量,静态变量
栈中局部变量表的引用变量
本地方法栈的引用变量
被同步锁持有的对象
栈中局部变量表的引用变量
本地方法栈的引用变量
被同步锁持有的对象
SafePoint
用户线程只会运行至安全点暂停
常用命令
jps
查看java进程
jinfo
实时查看修改JVM参数
manageable的flag才能实时修改
manageable的flag才能实时修改
jstat
查看类装载信息
查看垃圾收集信息
jstack
查看线程堆栈信息,可以发现程序死锁
通过jstack解决CPU100的情况
jmap
生成Dump文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath="D:/LYT/gc dump/dump.hprof"
指定参数,发送内存溢出是自动生成dump文件
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath="D:/LYT/gc dump/dump.hprof"
指定参数,发送内存溢出是自动生成dump文件
jmap -heap PID 查看堆内存使用情况
jconsole
可视化查看,还可以看mbeans
jvisualvm
监控本地/远程java进程
并发编程/异步编程
volatile
可见性
有序性
双重检查锁创建单例的安全问题
memory = allocate()
分配对象内存空间
分配对象内存空间
initialize(memory)
初始化对象
初始化对象
instance = memory
设置instance指向刚分配好的内存空间
此时instance != null
设置instance指向刚分配好的内存空间
此时instance != null
原理
高速缓存架构
CPU多级缓存 - 解决CPU与主存之间读写速度不匹配的问题
MESI协议
Modify
数据被修改,只存于当前缓存行中,与主存数据不一致
缓存行必须时刻监听所有试图读该缓存行相对应的内存的操作
其他缓存须在本缓存行写回内存并将状态置为E之后才能操作该缓存行对应的内存数据
缓存行必须时刻监听所有试图读该缓存行相对应的内存的操作
其他缓存须在本缓存行写回内存并将状态置为E之后才能操作该缓存行对应的内存数据
疑问:若变为E才读取内存数据
那While循环应该就是最终一致性的,但测试while循环无法停止
那While循环应该就是最终一致性的,但测试while循环无法停止
Exclusive
数据只存在于当前缓存行中,与内存数据一致
缓存行必须监听其他缓存读主内存中该缓存行相对应的内存的操作
一旦有这种操作,该缓存行需要变成S状态
缓存行必须监听其他缓存读主内存中该缓存行相对应的内存的操作
一旦有这种操作,该缓存行需要变成S状态
Shared
数据被多个CPU缓存,且各个缓存中的数据与主存一致
监听缓存行的修改,将当前缓存行置为无效状态
监听缓存行的修改,将当前缓存行置为无效状态
Invalid
表示当前缓存行已失效
总线嗅探机制
前提:所有内存的传输都发生在一条共享的总线上
目标:CPU缓存不停嗅探总线上发生的数据交换,跟踪其他CPU缓存做了什么
当发现数据变更时,且当前CPU缓存存在这份数据,则令其失效
当发现数据变更时,且当前CPU缓存存在这份数据,则令其失效
MESI优化
Store Buffer
CPU写入Store Buffer,由SB发送通知接收ACK,然后写入缓存
此时CPU不再需要等待invalid的ack
执行下一个指令,导致执行重排序
此时CPU不再需要等待invalid的ack
执行下一个指令,导致执行重排序
invalid queue
用于失效CPU缓存行
内存屏障
读屏障 - 执行invalid queue,后续从主存读取数据
写屏障 - 执行Store Buffer,将数据直接写入主存
并且解决了指令重排,重排序其实优化了缓存行的使用
并且解决了指令重排,重排序其实优化了缓存行的使用
0x0000000002931351: lock add dword ptr [rsp],0h ;*putstatic instance
x86 在写入static变量时会设置store load屏障,此时会使用lock汇编指令
x86 只有FIFO的写缓存,没有无效缓存,所以只响应store load就可以了
x86 唯一可能出现乱序的就是Store Load,因为写是异步发生
x86 在写入static变量时会设置store load屏障,此时会使用lock汇编指令
x86 只有FIFO的写缓存,没有无效缓存,所以只响应store load就可以了
x86 唯一可能出现乱序的就是Store Load,因为写是异步发生
volatile根据不同操作系统和CPU架构生成不同的内存屏障指令
总结
Thread-A
Thread-A发出LOCK#指令加锁
发出的LOCK#指令锁总线(或锁缓存行)
1) 令Thread-B高速缓存中的缓存行内容失效
2) Thread-A向主存回写最新修改的值
1) 令Thread-B高速缓存中的缓存行内容失效
2) Thread-A向主存回写最新修改的值
锁释放
Thread-B
发现对应地址的缓存行被锁了,等待锁的释放,缓存一致性协议会保证它读取到最新的值
JMM
提供了可见性和有序性的解决方案
重排序:
源代码 -> 编译器优化重排序 -> 指令重排序 -> 内存系统重排序 -> 最终执行的指令序列
源代码 -> 编译器优化重排序 -> 指令重排序 -> 内存系统重排序 -> 最终执行的指令序列
可见性:重排序导致了可见性问题
happens before
程序顺序规则
注意:与as-if-serial的区别
注意:与as-if-serial的区别
对于单个线程中的每个操作,前继操作happens-before于该线程中的任意后续操作
监视器锁规则
对一个锁的解锁,happens-before于随后对这个锁的加锁
volatile变量规则
volatile变量的写操作,happens-before这个变量的读操作
传递性
如果A happens-before B,且B happens-before C,那么A happens-before C
线程启动原则
在主线程A执行过程中,启动子线程B,那么线程A在启动子线程B之前对共享变量的修改结果对线程B可见
线程中断原则
A调用B的interrupt,A对中断标识的修改对B可见
线程终止规则
线程中所有的操作都happens-before线程的终止检测,如Thread.join() Thread.isAlive()
对象终结规则
一个对象的初始化完成happens-before它的finalize()方法开始
finally原则
内存屏障保证
简单的说就是我们只需要满足这个原则,而不用关心每个原则的底层实现是什么,如不用了解volatile的实现原理
通过内存屏障保证可见性
通过内存屏障保证可见性
synchronized
满足原子性和可见性(happens before原则之一)
对象锁,类锁(锁的是类的Class对象)
实现原理
Mark Word
组成部分,重量级锁需要使用ObjectMonitor,类似于AQS
Monitor
指令
monitorenter - 获取锁
相当于调用lock方法
相当于调用lock方法
monitorexit - 释放锁
相当于调用unlock方法
相当于调用unlock方法
锁升级
偏向锁
CAS获取锁的线程再次获取锁时
只需检查锁标志位为偏向锁
并比较当前线程ID是否等于头部记录的线程ID
只需检查锁标志位为偏向锁
并比较当前线程ID是否等于头部记录的线程ID
轻量级锁
偏向锁竞争才会释放,偏向锁撤销需等待全局安全点,暂停偏向锁线程
若此时持锁线程还活着,则升级为轻量级锁
若此时持锁线程还活着,则升级为轻量级锁
加锁
栈帧生成锁记录LockRecord,存储一份MarkWord的拷贝
尝试通过CAS操作将Mark Word指向LockRecord
并将Lock Record里的owner指针指向Mark Word
更新失败则判断Mark Word是否已经指向栈帧
若指向,则代表已经获取了轻量级锁
尝试通过CAS操作将Mark Word指向LockRecord
并将Lock Record里的owner指针指向Mark Word
更新失败则判断Mark Word是否已经指向栈帧
若指向,则代表已经获取了轻量级锁
解锁
若Mark Word仍指向栈帧,通过CAS替换Mark Word,此时可能替换失败
未指向和替换失败代表此时锁已经升级为重量级锁
需要唤醒锁阻塞队列的下一个等待线程
未指向和替换失败代表此时锁已经升级为重量级锁
需要唤醒锁阻塞队列的下一个等待线程
重量级锁
当多次获取轻量级锁失败时,升级为重量级锁
进入Monitor的锁阻塞队列,等待持锁节点唤醒
进入Monitor的锁阻塞队列,等待持锁节点唤醒
优化
锁消除
锁粗化
早期依赖于操作系统实现
线程间切换需要由用户态转为核心态
线程间切换需要由用户态转为核心态
ThreadPoolExecutor
状态转换
RUNNING:接受新任务并且处理阻塞队列里的任务;
SHUTDOWN:拒绝新任务但是处理阻塞队列里的任务;
STOP:拒绝新任务并且抛弃阻塞队列里的任务,同时会中断正在处理的任务;
TIDYING:所有任务都执行完(包含阻塞队列里面任务)当前线程池活动线程为 0,将要调用 terminated 方法;
TERMINATED:终止状态,terminated方法调用完成以后的状态。
源码解析
参数
ctl
用来标记线程池状态(高3位),线程个数(低29位)
默认是RUNNING状态,线程个数为0
用来标记线程池状态(高3位),线程个数(低29位)
默认是RUNNING状态,线程个数为0
https://www.cnblogs.com/huangjuncong/p/10031525.html
execute
添加任务执行
addWorker
添加工作线程
执行流程
线程池使用ThreadLocal导致内存溢出
不去调用remove,导致所有的线程一直都会持有对象
不去调用remove,导致所有的线程一直都会持有对象
ThreadLocal
原理图
内存泄露问题
线程池使用ThreadLocal导致内存溢出
不去调用remove,导致所有的线程一直都会持有对象
不去调用remove,导致所有的线程一直都会持有对象
CHM
HashMap
参数
链表长度除去头结点大于8转为红黑树
小于6退化
转红黑树的另一个前提元素个数大于64
当不满足该条件时只会发生扩容
小于6退化
转红黑树的另一个前提元素个数大于64
当不满足该条件时只会发生扩容
流程
延迟初始化:put -> resize(初始化/扩容)
-> p = tab[i = (n - 1) & hash](算出元素下标)
-> p = tab[i = (n - 1) & hash](算出元素下标)
优化
使元素分布更均匀:高低位异或
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
(key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16)
扩容
jdk8 -> 高低位尾插法,不会无限循环
JDK1.7,在扩容时由于顺序颠倒,可能导致环形链表的产生,导致无限循环
CHM
参数
sizeCtl:
负数代表扩容或初始化
-1 代表初始化
-n 代表有n-1个线程进行扩容操作
0代表还未被初始化
初始化后这个值代表下一次初始化的大小
负数代表扩容或初始化
-1 代表初始化
-n 代表有n-1个线程进行扩容操作
0代表还未被初始化
初始化后这个值代表下一次初始化的大小
流程
优化
CAS + synchronized
区间划分,协助扩容
addCount
CounterCell
CounterCell
1.7 分段锁,二次Hash
AQS
独占
void acquire(int arg)
void acquireInterruptibly(int arg)
boolean release(int arg)
void acquireInterruptibly(int arg)
boolean release(int arg)
tryAcquire一般实现:
CAS修改state状态
设置当前持锁线程
下次获取是重入
acquire:
当已被其他线程持有
独占模式进入阻塞队列并等待前一个节点唤醒
唤醒后设置当前节点为head
CAS修改state状态
设置当前持锁线程
下次获取是重入
acquire:
当已被其他线程持有
独占模式进入阻塞队列并等待前一个节点唤醒
唤醒后设置当前节点为head
tryRelease一般实现:
减少重入次数,当减少为0时,清空当前持锁线程
release:
尝试释放锁成功则唤醒下个节点
减少重入次数,当减少为0时,清空当前持锁线程
release:
尝试释放锁成功则唤醒下个节点
共享
void acquireShared(int arg)
void acquireSharedInterruptibly(int arg)
boolean releaseShared(int arg)
void acquireSharedInterruptibly(int arg)
boolean releaseShared(int arg)
tryAcquireShared一般实现:
自选通过CAS减少State值
acquireShared:
获取失败
共享模式进入锁阻塞队列并等待前一个节点唤醒
唤醒后设置当前节点为head
并进行传递,唤醒下个共享节点
自选通过CAS减少State值
acquireShared:
获取失败
共享模式进入锁阻塞队列并等待前一个节点唤醒
唤醒后设置当前节点为head
并进行传递,唤醒下个共享节点
tryReleaseShared一般实现:
CAS增加state个数
releaseShared:
尝试成功唤醒下个节点
CAS增加state个数
releaseShared:
尝试成功唤醒下个节点
ConditionObject
单向链表存储条件队列
await 释放锁进入条件队列
signal 将条件队列的头部节点转移至AQS队列并唤醒让其尝试获取锁
await 释放锁进入条件队列
signal 将条件队列的头部节点转移至AQS队列并唤醒让其尝试获取锁
Lock
ReentrantLock
ReentrantReadWriteLock
高16位:共享锁状态
低16位:互斥锁状态
低16位:互斥锁状态
CopyOnWriteArrayList
写时拷贝,读快照
其他
CountDownLatch 闭锁
CyclicBarrier 栅栏
Semaphore 信号量
Exchanger 交换器
BlockingQueue
实现
设计模式
// TODO
Zookeeper
集群模式启动
https://www.processon.com/view/link/60603dfb1e0853028ab273a5
https://www.processon.com/view/link/60603dfb1e0853028ab273a5
IO通信模型
https://www.processon.com/view/link/6061913c7d9c08555e684073
https://www.processon.com/view/link/6061913c7d9c08555e684073
Leader选举和客户端请求处理
https://www.processon.com/view/link/6062e663079129148ce4a71f
https://www.processon.com/view/link/6062e663079129148ce4a71f
Watch机制的存储与转发
https://www.processon.com/view/link/607d37597d9c08283dd3b312
https://www.processon.com/view/link/607d37597d9c08283dd3b312
Kafka/Cloud stream
Kafka消息传递
https://www.processon.com/view/link/607d37a4079129368891ca82
https://www.processon.com/view/link/607d37a4079129368891ca82
Cloud Stream的启动与坑
https://www.processon.com/view/link/607077a25653bb4d47999d0d
https://www.processon.com/view/link/607077a25653bb4d47999d0d
问题记录
beanFactory.registerBeanDefinition
beanFactory.registerSingleton
注册顺序问题
beanFactory.registerSingleton
注册顺序问题
注册Bean定义和注册单例Bean时的顺序问题
若先注册单例,会被保存至手工单例容器中
此后注册Bean定义,会将手工单例容器中的Bean移至单例Bean容器中
最后会重置Bena定义,将单例Bean容器中的Bean Remove掉
此时相当于由容器管理我们Bean的创建,而手动注入的方式是为了自己管理Bean的创建
此后注册Bean定义,会将手工单例容器中的Bean移至单例Bean容器中
最后会重置Bena定义,将单例Bean容器中的Bean Remove掉
此时相当于由容器管理我们Bean的创建,而手动注入的方式是为了自己管理Bean的创建
SpringCloudStream
Binder启动时消息队列尚未启动会导致启动线程阻塞,即Tomcat无法监听端口
Binder启动时消息队列尚未启动会导致启动线程阻塞,即Tomcat无法监听端口
@Async无法解决循环依赖
对于prototype类型的Bean,其销毁方法并不会由Spring管理
出现循环依赖时,不允许在Bean初始化时替换对象,否则各对象之间状态不一致
只能在三级缓存放入二级缓存的那一刻替换代理对象
只能在三级缓存放入二级缓存的那一刻替换代理对象
0 条评论
下一页