1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-08-05 20:31:37 +08:00

Compare commits

..

10 Commits

Author SHA1 Message Date
Guide
e778f21f6f [docs update]添加问题:什么情况下会出现主从延迟?如何尽量减少延迟? 2023-09-26 16:05:43 +08:00
Guide
896bb24542
Merge pull request #2178 from Nu11P0interExcepti0n/Nu11P0interExcepti0n-patch-1
Update cdn.md
2023-09-26 09:32:14 +08:00
菜路路
f4c4febc4d
Update cdn.md
笔误,多打了一个字
2023-09-25 22:45:00 +08:00
Guide
5506781c68
Merge pull request #2172 from paigeman/paigeman-patch-1
chore: fix typos
2023-09-25 16:49:13 +08:00
Guide
c939acf5e9
Merge pull request #2173 from chesha1/main
Fix typo of TLB
2023-09-25 16:48:53 +08:00
Guide
e3ef7e2852 [docs fix]一些小错误 2023-09-25 16:48:16 +08:00
Guide
b2ae0fde18 [docs update&fix]添加并发面试问题:如何设计一个能够根据任务的优先级来执行的线程池?& 修复一些笔误 2023-09-25 15:51:13 +08:00
chesha1
3b439ef19b Fix typo of TLB 2023-09-22 00:44:48 +08:00
paigeman
22c685b2e1
Update index-invalidation-caused-by-implicit-conversion.md 2023-08-24 09:37:26 +08:00
paigeman
0e0dc5dc0f
Update mysql-query-execution-plan.md 2023-08-23 09:54:01 +08:00
13 changed files with 114 additions and 49 deletions

View File

@ -227,7 +227,7 @@ MMU 将虚拟地址翻译为物理地址的主要机制有 3 种:
#### TLB 有什么用?使用 TLB 之后的地址翻译流程是怎样的?
为了提高虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **转址旁路缓存(Translation Lookasjde BufferTLB也被称为快表)** 。
为了提高虚拟地址到物理地址的转换速度,操作系统在 **页表方案** 基础之上引入了 **转址旁路缓存(Translation Lookaside BufferTLB也被称为快表)** 。
![加入 TLB 之后的地址翻译](https://oss.javaguide.cn/github/javaguide/cs-basics/operating-system/physical-virtual-address-translation-mmu.png)

View File

@ -117,9 +117,9 @@ CALL pre_test1();
根据官方文档的描述,我们的第 23 两条 SQL 都发生了隐式转换,第 2 条 SQL 的查询条件`num1 = '10000'`,左边是`int`类型右边是字符串,第 3 条 SQL 相反,那么根据官方转换规则第 7 条,左右两边都会转换为浮点数再进行比较。
先看第 2 条 SQL`SELECT * FROM`test1`WHERE num1 = '10000';` **左边为 int 类型**`10000`,转换为浮点数还是`10000`,右边字符串类型`'10000'`,转换为浮点数也是`10000`。两边的转换结果都是唯一确定的,所以不影响使用索引。
先看第 2 条 SQL``SELECT * FROM `test1` WHERE num1 = '10000';`` **左边为 int 类型**`10000`,转换为浮点数还是`10000`,右边字符串类型`'10000'`,转换为浮点数也是`10000`。两边的转换结果都是唯一确定的,所以不影响使用索引。
第 3 条 SQL`SELECT * FROM`test1`WHERE num2 = 10000;` **左边是字符串类型**`'10000'`,转浮点数为 10000 是唯一的,右边`int`类型`10000`转换结果也是唯一的。但是,因为左边是检索条件,`'10000'`转到`10000`虽然是唯一,但是其他字符串也可以转换为`10000`,比如`'10000a'``'010000'``'10000'`等等都能转为浮点数`10000`,这样的情况下,是不能用到索引的。
第 3 条 SQL``SELECT * FROM `test1` WHERE num2 = 10000;`` **左边是字符串类型**`'10000'`,转浮点数为 10000 是唯一的,右边`int`类型`10000`转换结果也是唯一的。但是,因为左边是检索条件,`'10000'`转到`10000`虽然是唯一,但是其他字符串也可以转换为`10000`,比如`'10000a'``'010000'``'10000'`等等都能转为浮点数`10000`,这样的情况下,是不能用到索引的。
关于这个**隐式转换**我们可以通过查询测试验证一下,先插入几条数据,其中`num2='10000a'``'010000'``'10000'`
@ -129,7 +129,7 @@ INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VAL
INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VALUES ('10000003', '10000', ' 10000', '0', '0', '2df3d9465ty2e4hd523', '2df3d9465ty2e4hd523');
```
然后使用第三条 SQL 语句`SELECT * FROM`test1`WHERE num2 = 10000;`进行查询:
然后使用第三条 SQL 语句``SELECT * FROM `test1` WHERE num2 = 10000;``进行查询:
![](https://oss.javaguide.cn/github/javaguide/mysqlindex-invalidation-caused-by-implicit-conversion-03.png)
@ -144,7 +144,7 @@ INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VAL
如此也就印证了之前的查询结果了。
再次写一条 SQL 查询 str1 字段:`SELECT * FROM`test1`WHERE str1 = 1234;`
再次写一条 SQL 查询 str1 字段:``SELECT * FROM `test1` WHERE str1 = 1234;``
![](https://oss.javaguide.cn/github/javaguide/mysqlindex-invalidation-caused-by-implicit-conversion-05.png)
@ -159,4 +159,4 @@ INSERT INTO `test1` (`id`, `num1`, `num2`, `type1`, `type2`, `str1`, `str2`) VAL
所以,我们在写 SQL 时一定要养成良好的习惯,查询的字段是什么类型,等号右边的条件就写成对应的类型。特别当查询的字段是字符串时,等号右边的条件一定要用引号引起来标明这是一个字符串,否则会造成索引失效触发全表扫描。
<!-- @include: @article-footer.snippet.md -->
<!-- @include: @article-footer.snippet.md -->

View File

@ -89,7 +89,8 @@ id 如果相同从上往下依次执行。id 不同id 值越大,执行
查询用到的表名,每行都有对应的表名,表名除了正常的表之外,也可能是以下列出的值:
- **`<unionM,N>`** : 本行引用了 id 为 M 和 N 的行的 UNION 结果;
- **`<derivedN>`** : 本行引用了 id 为 N 的表所产生的的派生表结果。派生表有可能产生自 FROM 语句中的子查询。 -**`<subqueryN>`** : 本行引用了 id 为 N 的表所产生的的物化子查询结果。
- **`<derivedN>`** : 本行引用了 id 为 N 的表所产生的的派生表结果。派生表有可能产生自 FROM 语句中的子查询。
- **`<subqueryN>`** : 本行引用了 id 为 N 的表所产生的的物化子查询结果。
### type重要
@ -140,4 +141,4 @@ rows 列表示根据表统计信息及选用情况,大致估算出找到所需
- https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
- https://juejin.cn/post/6953444668973514789
<!-- @include: @article-footer.snippet.md -->
<!-- @include: @article-footer.snippet.md -->

View File

@ -33,7 +33,7 @@ head:
![阿里云文档https://help.aliyun.com/document_detail/64836.html](https://oss.javaguide.cn/github/javaguide/high-performance/cdn/cdn-aliyun-dcdn.png)
绝大部分公司都会在项目开发中使用 CDN 服务,但很少会有自建 CDN 服务的公司。基于成本、稳定性和易用性考虑,建议直接选择专业的云厂商(比如阿里云、腾讯云、华为云、青云)或者 CDN 厂商(比如网宿、蓝汛)提供的开箱即用的 CDN 服务。
绝大部分公司都会在项目开发中使用 CDN 服务,但很少会有自建 CDN 服务的公司。基于成本、稳定性和易用性考虑,建议直接选择专业的云厂商(比如阿里云、腾讯云、华为云、青云)或者 CDN 厂商(比如网宿、蓝汛)提供的开箱即用的 CDN 服务。
很多朋友可能要问了:**既然是就近访问,为什么不直接将服务部署在多个不同的地方呢?**
@ -132,4 +132,4 @@ http://cdn.wangsu.com/4/123.mp3? wsSecret=79aead3bd7b5db4adeffb93a010298b5&wsTim
- CDN 是个啥玩意?一文说个明白:<https://mp.weixin.qq.com/s/Pp0C8ALUXsmYCUkM5QnkQw>
- 《透视 HTTP 协议》- 37 | CDN加速我们的网络服务<http://gk.link/a/11yOG>
<!-- @include: @article-footer.snippet.md -->
<!-- @include: @article-footer.snippet.md -->

View File

@ -19,7 +19,7 @@ tag:
我们可以把消息队列看作是一个存放消息的容器,当我们需要使用消息的时候,直接从容器中取出消息供自己使用即可。由于队列 Queue 是一种先进先出的数据结构,所以消费消息时也是按照顺序来消费的。
![Message queue](https://oss.javaguide.cn/%E6%B6%88%E6%81%AF%E9%98%9F%E5%88%97/message-queue-small.png)
![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/message-queue-small.png)
参与消息传递的双方称为 **生产者****消费者** ,生产者负责发送消息,消费者负责处理消息。

View File

@ -22,34 +22,6 @@ head:
一般情况下,我们都会选择一主多从,也就是一台主数据库负责写,其他的从数据库负责读。主库和从库之间会进行数据同步,以保证从库中数据的准确性。这样的架构实现起来比较简单,并且也符合系统的写少读多的特点。
### 读写分离会带来什么问题?如何解决?
读写分离对于提升数据库的并发非常有效,但是,同时也会引来一个问题:主库和从库的数据存在延迟,比如你写完主库之后,主库的数据同步到从库是需要时间的,这个时间差就导致了主库和从库的数据不一致性问题。这也就是我们经常说的 **主从同步延迟**
主从同步延迟问题的解决,没有特别好的一种方案(可能是我太菜了,欢迎评论区补充)。你可以根据自己的业务场景,参考一下下面几种解决办法。
**1.强制将读请求路由到主库处理。**
既然你从库的数据过期了,那我就直接从主库读取嘛!这种方案虽然会增加主库的压力,但是,实现起来比较简单,也是我了解到的使用最多的一种方式。
比如 `Sharding-JDBC` 就是采用的这种方案。通过使用 Sharding-JDBC 的 `HintManager` 分片键值管理器,我们可以强制使用主库。
```java
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
// 继续JDBC操作
```
对于这种方案,你可以将那些必须获取最新数据的读请求都交给主库处理。
**2.延迟读取。**
还有一些朋友肯定会想既然主从同步存在延迟,那我就在延迟之后读取啊,比如主从同步延迟 0.5s,那我就 1s 之后再读取数据。这样多方便啊!方便是方便,但是也很扯淡。
不过,如果你是这样设计业务流程就会好很多:对于一些对数据比较敏感的场景,你可以在完成写请求之后,避免立即进行请求操作。比如你支付成功之后,跳转到一个支付成功的页面,当你点击返回之后才返回自己的账户。
另外,[《MySQL 实战 45 讲》](https://time.geekbang.org/column/intro/100020801?code=ieY8HeRSlDsFbuRtggbBQGxdTh-1jMASqEIeqzHAKrI%3D)这个专栏中的[《读写分离有哪些坑?》](https://time.geekbang.org/column/article/77636)这篇文章还介绍了很多其他比较实际的解决办法,感兴趣的小伙伴可以自行研究一下。
### 如何实现读写分离?
不论是使用哪一种读写分离具体的实现方案,想要实现读写分离一般包含如下几步:
@ -105,6 +77,69 @@ MySQL binlog(binary log 即二进制日志文件) 主要记录了 MySQL 数据
**MySQL 主从复制是依赖于 binlog 。另外,常见的一些同步 MySQL 数据到其他数据源的工具(比如 canal的底层一般也是依赖 binlog 。**
### 如何避免主从延迟?
读写分离对于提升数据库的并发非常有效,但是,同时也会引来一个问题:主库和从库的数据存在延迟,比如你写完主库之后,主库的数据同步到从库是需要时间的,这个时间差就导致了主库和从库的数据不一致性问题。这也就是我们经常说的 **主从同步延迟**
如果我们的业务场景无法容忍主从同步延迟的话,应该如何避免呢?
这里提供两种我知道的方案(能力有限,欢迎补充),你可以根据自己的业务场景参考一下。
**1.强制将读请求路由到主库处理。**
既然你从库的数据过期了,那我就直接从主库读取嘛!这种方案虽然会增加主库的压力,但是,实现起来比较简单,也是我了解到的使用最多的一种方式。
比如 `Sharding-JDBC` 就是采用的这种方案。通过使用 Sharding-JDBC 的 `HintManager` 分片键值管理器,我们可以强制使用主库。
```java
HintManager hintManager = HintManager.getInstance();
hintManager.setMasterRouteOnly();
// 继续JDBC操作
```
对于这种方案,你可以将那些必须获取最新数据的读请求都交给主库处理。
**2.延迟读取。**
还有一些朋友肯定会想既然主从同步存在延迟,那我就在延迟之后读取啊,比如主从同步延迟 0.5s,那我就 1s 之后再读取数据。这样多方便啊!方便是方便,但是也很扯淡。
不过,如果你是这样设计业务流程就会好很多:对于一些对数据比较敏感的场景,你可以在完成写请求之后,避免立即进行请求操作。比如你支付成功之后,跳转到一个支付成功的页面,当你点击返回之后才返回自己的账户。
另外,[《MySQL 实战 45 讲》](https://time.geekbang.org/column/intro/100020801?code=ieY8HeRSlDsFbuRtggbBQGxdTh-1jMASqEIeqzHAKrI%3D)这个专栏中的[《读写分离有哪些坑?》](https://time.geekbang.org/column/article/77636)这篇文章还介绍了很多其他比较实际的解决办法,感兴趣的小伙伴可以自行研究一下。
### 什么情况下会出现主从延迟?如何尽量减少延迟?
我们在上面的内容中也提到了主从延迟以及解决办法,这里我们再来详细分析一下主从延迟出现的原因以及应该如何尽量减少延迟。
要搞懂什么情况下会出现主从延迟,我们需要先搞懂什么是主从延迟。
MySQL 主从同步延时是指从库的数据落后于主库的数据,这种情况可能由以下两个原因造成:
1. 从库 I/O 线程接收 binlog 的速度跟不上主库写入 binlog 的速度,导致从库 relay log 的数据滞后于主库 binlog 的数据;
2. 从库 SQL 线程执行 relay log 的速度跟不上从库 I/O 线程接收 binlog 的速度,导致从库的数据滞后于从库 relay log 的数据。
与主从同步有关的时间点主要有 3 个:
1. 主库执行完一个事务,写入 binlog将这个时刻记为 T1
2. 从库 I/O 线程接收到 binlog 并写入 relay log 的时刻记为 T2
3. 从库 SQL 线程读取 relay log 同步数据本地的时刻记为 T3。
结合我们上面讲到的主从复制原理,可以得出:
- T2 和 T1 的差值反映了从库 I/O 线程的性能和网络传输的效率,这个差值越小说明从库 I/O 线程的性能和网络传输效率越高。
- T3 和 T2 的差值反映了从库 SQL 线程执行的速度,这个差值越小,说明从库 SQL 线程执行速度越快。
那什么情况下会出现出从延迟呢?这里列举几种常见的情况:
1. **从库机器性能比主库差**:从库接收 binlog 并写入 relay log 以及执行 SQL 语句的速度会比较慢(也就是 T2-T1 和 T3-T2 的值会较大),进而导致延迟。解决方法是选择与主库一样规格或更高规格的机器作为从库,或者对从库进行性能优化,比如调整参数、增加缓存、使用 SSD 等。
2. **从库处理的读请求过多**:从库需要执行主库的所有写操作,同时还要响应读请求,如果读请求过多,会占用从库的 CPU、内存、网络等资源影响从库的复制效率也就是 T2-T1 和 T3-T2 的值会较大,和前一种情况类似)。解决方法是引入缓存(推荐)、使用一主多从的架构,将读请求分散到不同的从库,或者使用其他系统来提供查询的能力,比如将 binlog 接入到 Hadoop、Elasticsearch 等系统中。
3. **大事务**:运行时间比较长,长时间未提交的事务就可以称为大事务。由于大事务执行时间长,并且从库上的大事务会比主库上的大事务花费更多的时间和资源,因此非常容易造成主从延迟。解决办法是避免大批量修改数据,尽量分批进行。类似的情况还有执行时间较长的慢 SQL ,实际项目遇到慢 SQL 应该进行优化。
4. **从库太多**:主库需要将 binlog 同步到所有的从库,如果从库数量太多,会增加同步的时间和开销(也就是 T2-T1 的值会比较大,但这里是因为主库同步压力大导致的)。解决方案是减少从库的数量,或者将从库分为不同的层级,让上层的从库再同步给下层的从库,减少主库的压力。
5. **网络延迟**:如果主从之间的网络传输速度慢,或者出现丢包、抖动等问题,那么就会影响 binlog 的传输效率,导致从库延迟。解决方法是优化网络环境,比如提升带宽、降低延迟、增加稳定性等。
6. **单线程复制**MySQL5.5 及之前只支持单线程复制。为了优化复制性能MySQL 5.6 引入了多线程复制MySQL 5.7 还进一步完善了多线程复制。
7. **复制模式**MySQL 默认的复制是异步的必然会存在延迟问题。全同步复制不存在延迟问题但性能太差了。半同步复制是一种折中方案相对于异步复制半同步复制提高了数据的安全性减少了主从延迟还是有一定程度的延迟。MySQL 5.5 开始MySQL 以插件的形式支持 semi-sync 半同步复制。并且MySQL 5.7 引入了增强半同步复制
8. ......
## 分库分表
读写分离主要应对的是数据库读并发,没有解决数据库存储问题。试想一下:**如果 MySQL 一张表的数据量过大怎么办?**

View File

@ -142,7 +142,7 @@ icon: jianli
项目介绍尽量压缩在两行之内,不需要介绍太多,但也不要随便几个字就介绍完了。
另外,个人收获和项目成果都是可选的,如果选择写的话,也不要花费太多篇幅,记住你的重点是介绍工作内容/个人职责。
**2、技术架构直接写技术名词就行不要再介绍技术是干嘛的了没意义属于无效介绍。**

View File

@ -351,9 +351,7 @@ public class BigDecimalUtil {
}
```
相关 issue[建议对保留规则设置为 RoundingMode.HALF_EVEN,即四舍六入五成双](https://github.com/Snailclimb/JavaGuide/issues/1122) 。
相关 issue[建议对保留规则设置为 RoundingMode.HALF_EVEN,即四舍六入五成双]([#1122](https://github.com/Snailclimb/JavaGuide/issues/1122)) 。
![RoundingMode.HALF_EVEN](https://oss.javaguide.cn/github/javaguide/java/basis/RoundingMode.HALF_EVEN.png)

View File

@ -81,7 +81,9 @@ public class RpcRequest implements Serializable {
**serialVersionUID 不是被 static 变量修饰了吗?为什么还会被“序列化”?**
`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。
~~`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。 `static` 变量是属于类的而不是对象。你反序列之后,`static` 变量的值就像是默认赋予给了对象一样,看着就像是 `static` 变量被序列化,实际只是假象罢了。~~
**🐛 修正(参见:[issue#2174](https://github.com/Snailclimb/JavaGuide/issues/2174)**`static` 修饰的变量是静态变量,位于方法区,本身是不会被序列化的。但是,`serialVersionUID` 的序列化做了特殊处理,在序列化时,会将 `serialVersionUID` 序列化到二进制字节流中;在反序列化时,也会解析它并做一致性判断。
官方说明如下:

View File

@ -700,7 +700,7 @@ CompletableFuture.runAsync(() -> {
### 合理组合多个异步任务
正确使用 `thenCompose()``thenCombine()```acceptEither()``allOf()``anyOf() `等方法来组合多个异步任务,以满足实际业务的需求,提高程序执行效率。
正确使用 `thenCompose()``thenCombine()``acceptEither()``allOf()``anyOf() `等方法来组合多个异步任务,以满足实际业务的需求,提高程序执行效率。
实际使用中,我们还可以利用或者参考现成的异步任务编排框架,比如京东的 [asyncTool](https://gitee.com/jd-platform-opensource/asyncTool) 。
@ -708,7 +708,7 @@ CompletableFuture.runAsync(() -> {
## 后记
这篇文章只是简单介绍了 `CompletableFuture` 比较常用的一些 API 。如果想要深入学习的话,还可以多找一些书籍和博客看,比如下面几篇文章就挺不错:
这篇文章只是简单介绍了 `CompletableFuture` 的核心概念和比较常用的一些 API 。如果想要深入学习的话,还可以多找一些书籍和博客看,比如下面几篇文章就挺不错:
- [CompletableFuture 原理与实践-外卖商家端 API 的异步化 - 美团技术团队](https://tech.meituan.com/2022/05/12/principles-and-practices-of-completablefuture.html):这篇文章详细介绍了 `CompletableFuture` 在实际项目中的运用。参考这篇文章,可以对项目中类似的场景进行优化,也算是一个小亮点了。这种性能优化方式比较简单且效果还不错!
- [读 RocketMQ 源码,学习并发编程三大神器 - 勇哥 java 实战分享](https://mp.weixin.qq.com/s/32Ak-WFLynQfpn0Cg0N-0A):这篇文章介绍了 RocketMQ 对`CompletableFuture`的应用。具体来说,从 RocketMQ 4.7 开始RocketMQ 引入了 `CompletableFuture`来实现异步消息处理 。

View File

@ -484,6 +484,35 @@ CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内
- **[Hippo4j](https://github.com/opengoofy/hippo4j)**:异步线程池框架,支持线程池动态变更&监控&报警,无需修改代码轻松引入。支持多种使用模式,轻松引入,致力于提高系统运行保障能力。
- **[Dynamic TP](https://github.com/dromara/dynamic-tp)**:轻量级动态线程池,内置监控告警功能,集成三方中间件线程池管理,基于主流配置中心(已支持 Nacos、ApolloZookeeper、Consul、Etcd可通过 SPI 自定义实现)。
### 如何设计一个能够根据任务的优先级来执行的线程池?
这是一个常见的面试问题,本质其实还是在考察求职者对于线程池以及阻塞队列的掌握。
我们上面也提到了,不同的线程池会选用不同的阻塞队列作为任务队列,比如`FixedThreadPool` 使用的是`LinkedBlockingQueue`(无界队列),由于队列永远不会被放满,因此`FixedThreadPool`最多只能创建核心线程数的线程。
假如我们需要实现一个优先级任务线程池的话,那可以考虑使用 `PriorityBlockingQueue` (优先级阻塞队列)作为任务队列(`ThreadPoolExecutor` 的构造函数有一个 `workQueue` 参数可以传入任务队列)。
![ThreadPoolExecutor构造函数](https://oss.javaguide.cn/github/javaguide/java/concurrent/common-parameters-of-threadpool-workqueue.jpg)
`PriorityBlockingQueue` 是一个支持优先级的无界阻塞队列,可以看作是线程安全的`PriorityQueue`,两者底层都是使用小顶堆形式的二叉堆,即值最小的元素优先出队。不过,`PriorityQueue` 不支持阻塞操作并且是有界的。
要想让 `PriorityBlockingQueue` 实现对任务的排序,传入其中的任务必须是具备排序能力的,方式有两种:
1. 提交到线程池的任务实现 `Comparable` 接口,并重写 `compareTo` 方法来指定任务之间的优先级比较规则。
2. 创建 `PriorityBlockingQueue` 时传入一个 `Comparator` 对象来指定任务之间的排序规则(推荐)。
不过,这存在一些风险和问题,比如:
- `PriorityBlockingQueue` 是无界的,可能堆积大量的请求,从而导致 OOM。
- 可能会导致饥饿问题,即低优先级的任务长时间得不到执行。
- 由于需要对队列中的元素进行排序操作,因此会降低性能。
对于 OOM 这个问题的解决比较简单粗暴,就是继承`PriorityBlockingQueue` 并重写一下 `offer` 方法(入队)的逻辑,当插入的元素数量超过指定值就返回 false 。
饥饿问题这个可以通过优化设计来解决(比较麻烦),比如等待时间过长的任务会被移除并重新添加到队列中,但是优先级会被提升。
对于性能方面的影响,是没办法避免的,毕竟需要对任务进行排序操作。并且,对于大部分业务场景来说,这点性能影响是可以接受的。
## Future
### Future 类有什么用?

View File

@ -89,8 +89,8 @@ JMM 说白了就是定义了一些规范来解决这些问题,开发者可以
**什么是主内存?什么是本地内存?**
- **主内存**:所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量还是方法中的本地变量(也称局部变量)
- **本地内存**:每个线程都有一个私有的本地内存来存储共享变量的副本,并且,每个线程只能访问自己的本地内存,无法访问其他线程的本地内存。本地内存是 JMM 抽象出来的一个概念,存储了主内存中的共享变量副本
- **主内存**:所有线程创建的实例对象都存放在主内存中,不管该实例对象是成员变量,还是局部变量,类信息、常量、静态变量都是放在主内存中。为了获取更好的运行速度,虚拟机及硬件系统可能会让工作内存优先存储于寄存器和高速缓存中。
- **本地内存**:每个线程都有一个私有的本地内存,本地内存存储了该线程以读 / 写共享变量的副本。每个线程只能操作自己本地内存中的变量,无法直接访问其他线程的本地内存。如果线程间需要通信,必须通过主内存来进行。本地内存是 JMM 抽象出来的一个概念,并不真实存在,它涵盖了缓存、写缓冲区、寄存器以及其他的硬件和编译器优化
Java 内存模型的抽象示意图如下:

View File

@ -100,7 +100,7 @@ AOP 之所以叫面向切面编程,是因为它的核心思想就是将横切
OOP 不能很好地处理一些分散在多个类或对象中的公共行为(如日志记录、事务管理、权限控制、接口限流、接口幂等等),这些行为通常被称为 **横切关注点cross-cutting concerns** 。如果我们在每个类或对象中都重复实现这些行为,那么会导致代码的冗余、复杂和难以维护。
AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从**核心业务逻辑core concerns核心关注点**中分离出来,实现关注点的分离。
AOP 可以将横切关注点(如日志记录、事务管理、权限控制、接口限流、接口幂等等)从 **核心业务逻辑core concerns核心关注点** 中分离出来,实现关注点的分离。
![](https://oss.javaguide.cn/github/javaguide/system-design/framework/spring/crosscut-logic-and-businesslogic-separation%20%20%20%20%20%20.png)