1
0
mirror of https://github.com/Snailclimb/JavaGuide synced 2025-06-16 18:10:13 +08:00

[docs update]picture url -> cdn

This commit is contained in:
Guide 2023-08-14 09:05:59 +08:00
parent 763d099acd
commit 895bf6ee2a
19 changed files with 78 additions and 79 deletions

View File

@ -15,7 +15,7 @@ ThoughtWorks 非常提倡分享、提倡帮助他人成长,这一点在公司
另外ThoughtWorks 也是一家非常提倡 Feedback (反馈) 文化的公司,反馈是告诉人们我们对他们的表现的看法以及他们应该如何更好地做到这一点。刚开始我并没有太在意,慢慢地自己确实感觉到正确的进行反馈对他人会有很大的帮助。因为人在做很多事情的时候,会很难发现别人很容易看到的一些小问题。就比如一个很有趣的现象一样,假如我们在做项目的时候没有测试这个角色,如果你完成了自己的模块,并且自己对这个模块测试了很多遍,你发现已经没啥问题了。但是,到了实际使用的时候会很大概率出现你之前从来没有注意的问题。解释这个问题的说法是:每个人的视野或多或少都是有盲点的,这与我们的关注点息息相关。对于自己做的东西,很多地方自己测试很多遍都不会发现,但是如果让其他人帮你进行测试的话,就很大可能会发现很多显而易见的问题。 另外ThoughtWorks 也是一家非常提倡 Feedback (反馈) 文化的公司,反馈是告诉人们我们对他们的表现的看法以及他们应该如何更好地做到这一点。刚开始我并没有太在意,慢慢地自己确实感觉到正确的进行反馈对他人会有很大的帮助。因为人在做很多事情的时候,会很难发现别人很容易看到的一些小问题。就比如一个很有趣的现象一样,假如我们在做项目的时候没有测试这个角色,如果你完成了自己的模块,并且自己对这个模块测试了很多遍,你发现已经没啥问题了。但是,到了实际使用的时候会很大概率出现你之前从来没有注意的问题。解释这个问题的说法是:每个人的视野或多或少都是有盲点的,这与我们的关注点息息相关。对于自己做的东西,很多地方自己测试很多遍都不会发现,但是如果让其他人帮你进行测试的话,就很大可能会发现很多显而易见的问题。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/feedback.png) ![](https://oss.javaguide.cn/github/about-the-author/feedback.png)
工作之后,平时更新公众号、专栏还有维护 Github 的时间变少了。实际上,很多时候下班回来后,都有自己的时间来干自己的事情,但是自己也总是找工作太累或者时间比较零散的接口来推掉了。到了今天,翻看 Github 突然发现 14 天前别人在 Github 上给我提的 PR 我还没有处理。这一点确实是自己没有做好的地方,没有合理安排好自己的时间。实际上自己有很多想写的东西,后面会慢慢将他们提上日程。工作之后,更加发现下班后的几个小时如何度过确实很重要 ,如果你觉得自己没有完成好自己白天该做的工作的话,下班后你可以继续忙白天没有忙完的工作,如果白天的工作对于你游刃有余的话,下班回来之后,你大可去干自己感兴趣的事情,学习自己感兴趣的技术。做任何事情都要基于自身的基础,切不可好高骛远。 工作之后,平时更新公众号、专栏还有维护 Github 的时间变少了。实际上,很多时候下班回来后,都有自己的时间来干自己的事情,但是自己也总是找工作太累或者时间比较零散的接口来推掉了。到了今天,翻看 Github 突然发现 14 天前别人在 Github 上给我提的 PR 我还没有处理。这一点确实是自己没有做好的地方,没有合理安排好自己的时间。实际上自己有很多想写的东西,后面会慢慢将他们提上日程。工作之后,更加发现下班后的几个小时如何度过确实很重要 ,如果你觉得自己没有完成好自己白天该做的工作的话,下班后你可以继续忙白天没有忙完的工作,如果白天的工作对于你游刃有余的话,下班回来之后,你大可去干自己感兴趣的事情,学习自己感兴趣的技术。做任何事情都要基于自身的基础,切不可好高骛远。

View File

@ -45,7 +45,9 @@ category: 走近作者
如果你也想通过接私活变现的话,可以在我的公众号后台回复“**接私活**”来了解一些我的个人经验分享。 如果你也想通过接私活变现的话,可以在我的公众号后台回复“**接私活**”来了解一些我的个人经验分享。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2020-8/1d38ea3b-da2a-41df-9ac4-087356e9b5b4-20200802185910087.png) <div align="center">
<img src="https://oss.javaguide.cn/github/javaguide/gongzhonghaoxuanchuan.png" style="margin: 0 auto;" />
</div>
## 为什么自称 Guide ## 为什么自称 Guide

View File

@ -32,7 +32,7 @@ https://leetcode-cn.com/problems/add-two-numbers/solution/
我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐 我们使用变量来跟踪进位,并从包含最低有效位的表头开始模拟逐
位相加的过程。 位相加的过程。
![图1对两数相加方法的可视化: 342 + 465 = 807 每个结点都包含一个数字,并且数字按位逆序存储。](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/34910956.jpg) ![图1对两数相加方法的可视化: 342 + 465 = 807 每个结点都包含一个数字,并且数字按位逆序存储。](https://oss.javaguide.cn/github/javaguide/cs-basics/algorithms/34910956.jpg)
### Solution ### Solution
@ -80,7 +80,7 @@ public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
> 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。 > 剑指 offer:输入一个链表,反转链表后,输出链表的所有元素。
![翻转链表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/81431871.jpg) ![翻转链表](https://oss.javaguide.cn/github/javaguide/cs-basics/algorithms/81431871.jpg)
### 问题分析 ### 问题分析
@ -246,7 +246,7 @@ public class Solution {
我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L 是列表的长度。只要我们找到列表的长度 L这个问题就很容易解决。 我们注意到这个问题可以容易地简化成另一个问题:删除从列表开头数起的第 (L - n + 1)个结点,其中 L 是列表的长度。只要我们找到列表的长度 L这个问题就很容易解决。
![图 1. 删除列表中的第 L - n + 1 个元素](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-9-20/94354387.jpg) ![图 1. 删除列表中的第 L - n + 1 个元素](https://oss.javaguide.cn/github/javaguide/cs-basics/algorithms/94354387.jpg)
### Solution ### Solution

View File

@ -573,7 +573,8 @@ public ListNode Merge(ListNode list1,ListNode list2) {
**栈:**后进先出LIFO **栈:**后进先出LIFO
**队列:** 先进先出 **队列:** 先进先出
很明显我们需要根据 JDK 给我们提供的栈的一些基本方法来实现。先来看一下 Stack 类的一些基本方法: 很明显我们需要根据 JDK 给我们提供的栈的一些基本方法来实现。先来看一下 Stack 类的一些基本方法:
![Stack类的一些常见方法](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-4-4/5985000.jpg)
![Stack类的一些常见方法](https://oss.javaguide.cn/github/javaguide/cs-basics/algorithms/5985000.jpg)
既然题目给了我们两个栈,我们可以这样考虑当 push 的时候将元素 push 进 stack1pop 的时候我们先把 stack1 的元素 pop 到 stack2然后再对 stack2 执行 pop 操作,这样就可以保证是先进先出的。(负[pop]负[pop]得正[先进先出] 既然题目给了我们两个栈,我们可以这样考虑当 push 的时候将元素 push 进 stack1pop 的时候我们先把 stack1 的元素 pop 到 stack2然后再对 stack2 执行 pop 操作,这样就可以保证是先进先出的。(负[pop]负[pop]得正[先进先出]

View File

@ -315,7 +315,7 @@ ps -A -ostat,ppid,pid,cmd |grep -e '^[Zz]'
下面通过一个实际的例子来模拟下图展示的线程死锁: 下面通过一个实际的例子来模拟下图展示的线程死锁:
![线程死锁示意图 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4%E6%AD%BB%E9%94%811.png) ![线程死锁示意图 ](https://oss.javaguide.cn/github/javaguide/java/2019-4%E6%AD%BB%E9%94%811-20230814005444749.png)
```java ```java
public class DeadLockDemo { public class DeadLockDemo {

View File

@ -28,14 +28,14 @@ Shell 编程在我们的日常开发工作中非常实用,目前 Linux 系统
另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。 另外,了解 shell 编程也是大部分互联网公司招聘后端开发人员的要求。下图是我截取的一些知名互联网公司对于 Shell 编程的要求。
![大型互联网公司对于shell编程技能的要求](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/60190220.jpg) ![大型互联网公司对于shell编程技能的要求](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/60190220.jpg)
### 什么是 Shell ### 什么是 Shell
简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。 简单来说“Shell 编程就是对一堆 Linux 命令的逻辑化处理”。
W3Cschool 上的一篇文章是这样介绍 Shell 的,如下图所示。 W3Cschool 上的一篇文章是这样介绍 Shell 的,如下图所示。
![什么是 Shell](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-26/19456505.jpg) ![什么是 Shell](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/19456505.jpg)
### Shell 编程的 Hello World ### Shell 编程的 Hello World
@ -59,7 +59,7 @@ shell 中 # 符号表示注释。**shell 的第一行比较特殊,一般都会
(4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。) (4) 运行脚本:`./helloworld.sh` 。(注意,一定要写成 `./helloworld.sh` ,而不是 `helloworld.sh` ,运行其它二进制的程序也一样,直接写 `helloworld.sh` linux 系统会去 PATH 里寻找有没有叫 helloworld.sh 的,而只有 /bin, /sbin, /usr/bin/usr/sbin 等在 PATH 里,你的当前目录通常不在 PATH 里,所以写成 `helloworld.sh` 是会找不到命令的,要用`./helloworld.sh` 告诉系统说,就在当前目录找。)
![shell 编程Hello World](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-16/55296212.jpg) ![shell 编程Hello World](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/55296212.jpg)
## Shell 变量 ## Shell 变量
@ -97,7 +97,7 @@ echo $hello
echo "helloworld!" echo "helloworld!"
``` ```
![使用自己定义的变量](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/19835037.jpg) ![使用自己定义的变量](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/19835037.jpg)
**Shell 编程中的变量名的命名的注意事项:** **Shell 编程中的变量名的命名的注意事项:**
@ -161,7 +161,7 @@ echo $greeting_2 $greeting_3
输出结果: 输出结果:
![输出结果](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-17/51148933.jpg) ![输出结果](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/51148933.jpg)
**获取字符串长度:** **获取字符串长度:**
@ -261,7 +261,7 @@ Shell 编程支持下面几种运算符
### 算数运算符 ### 算数运算符
![算数运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/4937342.jpg) ![算数运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/4937342.jpg)
我以加法运算符做一个简单的示例(注意:不是单引号,是反引号): 我以加法运算符做一个简单的示例(注意:不是单引号,是反引号):
@ -277,7 +277,7 @@ echo "Total value : $val"
关系运算符只支持数字,不支持字符串,除非字符串的值是数字。 关系运算符只支持数字,不支持字符串,除非字符串的值是数字。
![shell关系运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/64391380.jpg) ![shell关系运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/64391380.jpg)
通过一个简单的示例演示关系运算符的使用,下面 shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。 通过一个简单的示例演示关系运算符的使用,下面 shell 程序的作用是当 score=100 的时候输出 A 否则输出 B。
@ -301,7 +301,7 @@ B
### 逻辑运算符 ### 逻辑运算符
![逻辑运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60545848.jpg) ![逻辑运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/60545848.jpg)
示例: 示例:
@ -314,13 +314,13 @@ echo $a;
### 布尔运算符 ### 布尔运算符
![布尔运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/93961425.jpg) ![布尔运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/93961425.jpg)
这里就不做演示了,应该挺简单的。 这里就不做演示了,应该挺简单的。
### 字符串运算符 ### 字符串运算符
![ 字符串运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/309094.jpg) ![ 字符串运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/309094.jpg)
简单示例: 简单示例:
@ -344,7 +344,7 @@ a 不等于 b
### 文件相关运算符 ### 文件相关运算符
![文件相关运算符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-11-22/60359774.jpg) ![文件相关运算符](https://oss.javaguide.cn/github/javaguide/cs-basics/shell/60359774.jpg)
使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。 使用方式很简单,比如我们定义好了一个文件路径`file="/usr/learnshell/test.sh"` 如果我们想判断这个文件是否可读,可以这样`if [ -r $file ]` 如果想判断这个文件是否可写,可以这样`-w $file`,是不是很简单。

View File

@ -28,13 +28,13 @@ tag:
我来举个 🌰 吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。 我来举个 🌰 吧,比如我们有一个购票系统,需求是用户在购买完之后能接收到购买完成的短信。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef37fee7e09230.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef37fee7e09230.jpg)
我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。 我们省略中间的网络通信时间消耗,假如购票系统处理需要 150ms ,短信系统处理需要 200ms ,那么整个处理流程的时间消耗就是 150ms + 200ms = 350ms。
当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短信系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢? 当然,乍看没什么问题。可是仔细一想你就感觉有点问题,我用户购票在购票系统的时候其实就已经完成了购买,而我现在通过同步调用非要让整个请求拉长时间,而短信系统这玩意又不是很有必要,它仅仅是一个辅助功能增强用户体验感而已。我现在整个调用流程就有点 **头重脚轻** 的感觉了,购票是一个不太耗时的流程,而我现在因为同步调用,非要等待发送短信这个比较耗时的操作才返回结果。那我如果再加一个发送邮件呢?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef380429cf373e.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef380429cf373e.jpg)
这样整个系统的调用链又变长了,整个时间就变成了 550ms。 这样整个系统的调用链又变长了,整个时间就变成了 550ms。
@ -48,13 +48,11 @@ tag:
回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。 回想一下,我们在给大妈发送需要的信息之后我们是 **同步等待大妈给我配好饭菜** 的,上面我们只是加了鸡腿和土豆丝,万一我再加一个番茄牛腩,韭菜鸡蛋,这样是不是大妈打饭配菜的流程就会变长,我们等待的时间也会相应的变长。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/006APoFYly1fvd9cwjlfrj30as0b03ym.jpg)
那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以干其他事情了,这是一个 **异步** 的概念。 那后来,我们工作赚钱了有钱去饭店吃饭了,我们告诉服务员来一碗牛肉面加个荷包蛋 **(传达一个消息)** ,然后我们就可以在饭桌上安心的玩手机了 **(干自己其他事情)** ,等到我们的牛肉面上了我们就可以吃了。这其中我们也就传达了一个消息,然后我们又转过头干其他事情了。这其中虽然做面的时间没有变短,但是我们只需要传达一个消息就可以干其他事情了,这是一个 **异步** 的概念。
所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。 所以,为了解决这一个问题,聪明的程序员在中间也加了个类似于服务员的中间件——消息队列。这个时候我们就可以把模型给改造了。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38124f55eaea.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef38124f55eaea.jpg)
这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。 这样,我们在将消息存入消息队列之后我们就可以直接返回了(我们告诉服务员我们要吃什么然后玩手机),所以整个耗时只是 150ms + 10ms = 160ms。
@ -64,21 +62,21 @@ tag:
回到最初同步调用的过程,我们写个伪代码简单概括一下。 回到最初同步调用的过程,我们写个伪代码简单概括一下。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef381a505d3e1f.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef381a505d3e1f.jpg)
那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码? 那么第二步,我们又添加了一个发送邮件,我们就得重新去修改代码,如果我们又加一个需求:用户购买完还需要给他加积分,这个时候我们是不是又得改代码?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef381c4e1b1ac7.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef381c4e1b1ac7.jpg)
如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用? 如果你觉得还行,那么我这个时候不要发邮件这个服务了呢,我是不是又得改代码,又得重启应用?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef381f273a66bd.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef381f273a66bd.jpg)
这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。 这样改来改去是不是很麻烦,那么 **此时我们就用一个消息队列在中间进行解耦** 。你需要注意的是,我们后面的发送短信、发送邮件、添加积分等一些操作都依赖于上面的 `result` ,这东西抽象出来就是购票的处理结果呀,比如订单号,用户账号等等,也就是说我们后面的一系列服务都是需要同样的消息来进行处理。既然这样,我们是不是可以通过 **“广播消息”** 来实现。
我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。 我上面所讲的“广播”并不是真正的广播,而是接下来的系统作为消费者去 **订阅** 特定的主题。比如我们这里的主题就可以叫做 `订票` ,我们购买系统作为一个生产者去生产这条消息放入消息队列,然后消费者订阅了这个主题,会从消息队列中拉取消息并消费。就比如我们刚刚画的那张图,你会发现,在生产者这边我们只需要关注 **生产消息到指定主题中** ,而 **消费者只需要关注从指定主题中拉取消息** 就行了。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef382674b66892.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef382674b66892.jpg)
> 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。 > 如果没有消息队列,每当一个新的业务接入,我们都要在主系统调用新接口、或者当我们取消某些业务,我们也得在主系统删除某些接口调用。有了消息队列,我们只需要关心消息是否送达了队列,至于谁希望订阅,接下来收到消息如何处理,是下游的事情,无疑极大地减少了开发和联调的工作量。
@ -86,7 +84,7 @@ tag:
我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样? 我们再次回到一开始我们使用同步调用系统的情况,并且思考一下,如果此时有大量用户请求购票整个系统会变成什么样?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef382a9756bb1c.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef382a9756bb1c.jpg)
如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了? 如果,此时有一万的请求进入购票系统,我们知道运行我们主业务的服务器配置一般会比较好,所以这里我们假设购票系统能承受这一万的用户请求,那么也就意味着我们同时也会出现一万调用发短信服务的请求。而对于短信系统来说并不是我们的主要业务,所以我们配备的硬件资源并不会太高,那么你觉得现在这个短信系统能承受这一万的峰值么,且不说能不能承受,系统会不会 **直接崩溃** 了?
@ -128,13 +126,13 @@ tag:
可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊 😵? 可用性降低,复杂度上升,又带来一系列的重复消费,顺序消费,分布式事务,消息堆积的问题,这消息队列还怎么用啊 😵?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef382d709abc9d.png) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef382d709abc9d.png)
别急,办法总是有的。 别急,办法总是有的。
## RocketMQ 是什么? ## RocketMQ 是什么?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef383014430799.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef383014430799.jpg)
哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬 哇,你个混蛋!上面给我抛出那么多问题,你现在又讲 `RocketMQ` ,还让不让人活了?!🤬
@ -160,7 +158,7 @@ tag:
就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。 就像我们理解队列一样,消息中间件的队列模型就真的只是一个队列。。。我画一张图给大家理解。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3834ae653469.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3834ae653469.jpg)
在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。 在一开始我跟你提到了一个 **“广播”** 的概念,也就是说如果我们此时我们需要将一个消息发送给多个消费者(比如此时我需要将信息发送给短信系统和邮件系统),这个时候单个队列即不能满足需求了。
@ -176,7 +174,7 @@ tag:
其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。 其中,发布者将消息发送到指定主题中,订阅者需要 **提前订阅主题** 才能接受特定主题的消息。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3837887d9a54sds.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3837887d9a54sds.jpg)
### RocketMQ 中的消息模型 ### RocketMQ 中的消息模型
@ -186,7 +184,7 @@ tag:
所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。 所以,`RocketMQ` 中的 **主题模型** 到底是如何实现的呢?首先我画一张图,大家尝试着去理解一下。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef383d3e8c9788.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef383d3e8c9788.jpg)
我们可以看到在整个图中有 `Producer Group``Topic``Consumer Group` 三个角色,我来分别介绍一下他们。 我们可以看到在整个图中有 `Producer Group``Topic``Consumer Group` 三个角色,我来分别介绍一下他们。
@ -200,19 +198,19 @@ tag:
当然也可以消费者个数小于队列个数,只不过不太建议。如下图。 当然也可以消费者个数小于队列个数,只不过不太建议。如下图。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3850c808d707.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3850c808d707.jpg)
**每个消费组在每个队列上维护一个消费位置** ,为什么呢? **每个消费组在每个队列上维护一个消费位置** ,为什么呢?
因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。 因为我们刚刚画的仅仅是一个消费者组,我们知道在发布订阅模式中一般会涉及到多个消费者组,而每个消费者组在每个队列中的消费位置都是不同的。如果此时有多个消费者组,那么消息被一个消费者组消费完之后是不会删除的(因为其它消费者组也需要呀),它仅仅是为每个消费者组维护一个 **消费位移(offset)** ,每次消费者组消费完会返回一个成功的响应,然后队列再把维护的消费位移加一,这样就不会出现刚刚消费过的消息再一次被消费了。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3857fefaa079.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3857fefaa079.jpg)
可能你还有一个问题,**为什么一个主题中需要维护多个队列** 可能你还有一个问题,**为什么一个主题中需要维护多个队列**
答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。 答案是 **提高并发能力** 。的确,每个主题中只存在一个队列也是可行的。你想一下,如果每个主题中只存在一个队列,这个队列中也维护着每个消费者组的消费位置,这样也可以做到 **发布订阅模式** 。如下图。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38600cdb6d4b.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef38600cdb6d4b.jpg)
但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。 但是,这样我生产者是不是只能向一个队列发送消息?又因为需要维护消费位置所以一个队列只能对应一个消费者组中的消费者,这样是不是其他的 `Consumer` 就没有用武之地了?从这两个角度来讲,并发度一下子就小了很多。
@ -234,7 +232,7 @@ tag:
`Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。 `Topic` 消息量都比较均匀的情况下,如果某个 `broker` 上的队列越多,则该 `broker` 压力越大。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38687488a5a4.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef38687488a5a4.jpg)
> 所以说我们需要配置多个 Broker。 > 所以说我们需要配置多个 Broker。
@ -246,7 +244,7 @@ tag:
听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么? 听完了上面的解释你可能会觉得,这玩意好简单。不就是这样的么?
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef386c6d1e8bdb.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef386c6d1e8bdb.jpg)
嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer``Consumer``Broker` 直接进行生产消息,消费消息不就好了么? 嗯?你可能会发现一个问题,这老家伙 `NameServer` 干啥用的,这不多余吗?直接 `Producer``Consumer``Broker` 直接进行生产消息,消费消息不就好了么?
@ -258,7 +256,7 @@ tag:
当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。 当然,`RocketMQ` 中的技术架构肯定不止前面那么简单,因为上面图中的四个角色都是需要做集群的。我给出一张官网的架构图,大家尝试理解一下。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef386fa3be1e53.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef386fa3be1e53.jpg)
其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来 🤨。 其实和我们最开始画的那张乞丐版的架构图也没什么区别,主要是一些细节上的差别。听我细细道来 🤨。
@ -294,7 +292,7 @@ tag:
那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。 那么,我们现在使用了 **普通顺序模式** ,我们从上面学习知道了在 `Producer` 生产消息的时候会进行轮询(取决你的负载均衡策略)来向同一主题的不同消息队列发送消息。那么如果此时我有几个消息分别是同一个订单的创建、支付、发货,在轮询的策略下这 **三个消息会被发送到不同队列** ,因为在不同的队列此时就无法使用 `RocketMQ` 带来的队列有序特性来保证消息有序性了。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3874585e096e.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3874585e096e.jpg)
那么,怎么解决呢? 那么,怎么解决呢?
@ -324,7 +322,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
`RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。 `RocketMQ` 中使用的是 **事务消息加上事务反查机制** 来解决分布式事务问题的。我画了张图,大家可以对照着图进行理解。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38798d7a987f.png) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef38798d7a987f.png)
在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的** 在第一步发送的 half 消息 ,它的意思是 **在事务提交之前,对于消费者来说,这个消息是不可见的**
@ -346,7 +344,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
> >
> 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。 > 别忘了在 `RocketMQ` 中,**一个队列只会被一个消费者消费** ,如果你仅仅是增加消费者实例就会出现我一开始给你画架构图的那种情况。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef387d939ab66d.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef387d939ab66d.jpg)
## 什么是回溯消费? ## 什么是回溯消费?
@ -368,7 +366,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
### 同步刷盘和异步刷盘 ### 同步刷盘和异步刷盘
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef387fba311cda.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef387fba311cda-20230814005009889.jpg)
如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。 如上图所示,在同步刷盘中需要等待一个刷盘成功的 `ACK` ,同步刷盘对 `MQ` 消息可靠性来说是一种不错的保障,但是 **性能上会有较大影响** ,一般地适用于金融等特定业务场景。
@ -393,7 +391,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。 在单主从架构中,如果一个主节点挂掉了,那么也就意味着整个系统不能再生产了。那么这个可用性的问题能否解决呢?**一个主从不行那就多个主从的呗**,别忘了在我们最初的架构图中,每个 `Topic` 是分布在不同 `Broker` 中的。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef38687488a5a4.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef38687488a5asadasfg4.jpg)
但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点 A 负责的是订单 A 的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点 A 的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。 但是这种复制方式同样也会带来一个问题,那就是无法保证 **严格顺序** 。在上文中我们提到了如何保证的消息顺序性是通过将一个语义的消息发送在同一个队列中,使用 `Topic` 下的队列来保证顺序性的。如果此时我们主节点 A 负责的是订单 A 的一系列语义消息,然后它挂了,这样其他节点是无法代替主节点 A 的,如果我们任意节点都可以存入任何消息,那就没有顺序性可言了。
@ -413,7 +411,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
总结来说,整个消息存储的结构,最主要的就是 `CommitLoq``ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。 总结来说,整个消息存储的结构,最主要的就是 `CommitLoq``ConsumeQueue` 。而 `ConsumeQueue` 你可以大概理解为 `Topic` 中的队列。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef3884c02acc72.png) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef3884c02acc72.png)
`RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。 `RocketMQ` 采用的是 **混合型的存储结构** ,即为 `Broker` 单个实例下所有的队列共用一个日志数据文件来存储消息。有意思的是在同样高并发的 `Kafka` 中会为每个 `Topic` 分配一个存储文件。这就有点类似于我们有一大堆书需要装上书架,`RockeMQ` 是不分书的种类直接成批的塞上去的,而 `Kafka` 是将书本放入指定的分类区域的。
@ -423,7 +421,7 @@ emmm就两个字—— **幂等** 。在编程中一个*幂等* 操作的特
讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。 讲到这里,你可能对 `RockeMQ` 的存储架构还有些模糊,没事,我们结合着图来理解一下。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/16ef388763c25c62.jpg) ![](https://oss.javaguide.cn/github/javaguide/high-performance/message-queue/16ef388763c25c62.jpg)
emmm是不是有一点复杂 🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。 emmm是不是有一点复杂 🤣,看英文图片和英文文档的时候就不要怂,硬着头皮往下看就行。
@ -437,8 +435,6 @@ emmm是不是有一点复杂 🤣,看英文图片和英文文档的时候
因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考 🤔🤔 一下吧。 因为有一个知识点因为写嗨了忘讲了,想想在哪里加也不好,所以我留给大家去思考 🤔🤔 一下吧。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-11/e314ee45gy1g05zgr67bbj20gp0b3aba.jpg)
为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。 为什么 `CommitLog` 文件要设计成固定大小的长度呢?提醒:**内存映射机制**。
## 总结 ## 总结

View File

@ -212,9 +212,9 @@ Dubbo2.x 默认启用的序列化方式是 Hessian2 ,但是Dubbo 对 Hessian2
### 总结 ### 总结
Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:[https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/](https://dubbo.apache.org/zh/docs/v2.7/user/references/protocol/rest/)) Kryo 是专门针对 Java 语言序列化方式并且性能非常好,如果你的应用是专门针对 Java 语言的话可以考虑使用,并且 Dubbo 官网的一篇文章中提到说推荐使用 Kryo 作为生产环境的序列化方式。(文章地址:<https://cn.dubbo.apache.org/zh-cn/docsv2.7/user/serialization/>)。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2020-8/569e541a-22b2-4846-aa07-0ad479f07440.png) ![](https://oss.javaguide.cn/github/javaguide/java/569e541a-22b2-4846-aa07-0ad479f07440-20230814090158124.png)
像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。 像 Protobuf、 ProtoStuff、hessian 这类都是跨语言的序列化方式,如果有跨语言需求的话可以考虑使用。

View File

@ -13,7 +13,7 @@ Atomic 翻译成中文是原子的意思。在化学上,我们知道原子是
并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。 并发包 `java.util.concurrent` 的原子类都存放在`java.util.concurrent.atomic`下,如下图所示。
![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png) ![JUC原子类概览](https://oss.javaguide.cn/github/javaguide/java/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88.png)
根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类 根据操作的数据类型,可以将 JUC 包中的原子类分为 4 类

View File

@ -59,7 +59,7 @@ Java 提供的线程安全的 `Queue` 可以分为**阻塞队列**和**非阻塞
`BlockingQueue` 是一个接口,继承自 `Queue`,所以其实现类也可以作为 `Queue` 的实现来使用,而 `Queue` 又继承自 `Collection` 接口。下面是 `BlockingQueue` 的相关实现类: `BlockingQueue` 是一个接口,继承自 `Queue`,所以其实现类也可以作为 `Queue` 的实现来使用,而 `Queue` 又继承自 `Collection` 接口。下面是 `BlockingQueue` 的相关实现类:
![BlockingQueue 的实现类](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/51622268.jpg) ![BlockingQueue 的实现类](https://oss.javaguide.cn/github/javaguide/java/51622268.jpg)
下面主要介绍一下 3 个常见的 `BlockingQueue` 的实现类:`ArrayBlockingQueue``LinkedBlockingQueue``PriorityBlockingQueue` 下面主要介绍一下 3 个常见的 `BlockingQueue` 的实现类:`ArrayBlockingQueue``LinkedBlockingQueue``PriorityBlockingQueue`
@ -132,13 +132,13 @@ private static ArrayBlockingQueue<Integer> blockingQueue = new ArrayBlockingQueu
跳表的本质是同时维护了多个链表,并且链表是分层的, 跳表的本质是同时维护了多个链表,并且链表是分层的,
![2级索引跳表](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/93666217.jpg) ![2级索引跳表](https://oss.javaguide.cn/github/javaguide/java/93666217.jpg)
最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。 最低层的链表维护了跳表内所有的元素,每上面一层链表都是下面一层的子集。
跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素 18。 跳表内的所有链表的元素都是排序的。查找时,可以从顶级链表开始找。一旦发现被查找的元素大于当前链表中的取值,就会转入下一层链表继续找。这也就是说在查找过程中,搜索是跳跃式的。如上图所示,在跳表中查找元素 18。
![在跳表中查找元素18](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/18-12-9/32005738.jpg) ![在跳表中查找元素18](https://oss.javaguide.cn/github/javaguide/java/32005738.jpg)
查找 18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。 查找 18 的时候原来需要遍历 18 次,现在只需要 7 次即可。针对链表长度比较大的时候,构建索引查找效率的提升就会非常明显。

View File

@ -24,7 +24,7 @@ head:
如下图所示,在 Windows 中通过查看任务管理器的方式,我们就可以清楚看到 Windows 当前运行的进程(`.exe` 文件的运行)。 如下图所示,在 Windows 中通过查看任务管理器的方式,我们就可以清楚看到 Windows 当前运行的进程(`.exe` 文件的运行)。
![进程示例图片-Windows](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/进程示例图片-Windows.png) ![进程示例图片-Windows](https://oss.javaguide.cn/github/javaguide/java/%E8%BF%9B%E7%A8%8B%E7%A4%BA%E4%BE%8B%E5%9B%BE%E7%89%87-Windows.png)
### 何为线程? ### 何为线程?
@ -157,7 +157,7 @@ Java 线程状态变迁图(图源:[挑错 |《Java 并发编程的艺术》中
> >
> **为什么 JVM 没有区分这两种状态呢?** (摘自:[Java 线程运行怎么有第六种状态? - Dawell 的回答](https://www.zhihu.com/question/56494969/answer/154053599) 现在的时分time-sharing多任务multi-task操作系统架构通常都是用所谓的“时间分片time quantum or time slice”方式进行抢占式preemptive轮转调度round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。 > **为什么 JVM 没有区分这两种状态呢?** (摘自:[Java 线程运行怎么有第六种状态? - Dawell 的回答](https://www.zhihu.com/question/56494969/answer/154053599) 现在的时分time-sharing多任务multi-task操作系统架构通常都是用所谓的“时间分片time quantum or time slice”方式进行抢占式preemptive轮转调度round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。
![RUNNABLE-VS-RUNNING](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/RUNNABLE-VS-RUNNING.png) ![RUNNABLE-VS-RUNNING](https://oss.javaguide.cn/github/javaguide/java/RUNNABLE-VS-RUNNING.png)
- 当线程执行 `wait()`方法之后,线程进入 **WAITING等待** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。 - 当线程执行 `wait()`方法之后,线程进入 **WAITING等待** 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。
- **TIMED_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。 - **TIMED_WAITING(超时等待)** 状态相当于在等待状态的基础上增加了超时限制,比如通过 `sleeplong millis`方法或 `waitlong millis`方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。
@ -187,7 +187,7 @@ Java 线程状态变迁图(图源:[挑错 |《Java 并发编程的艺术》中
如下图所示,线程 A 持有资源 2线程 B 持有资源 1他们同时都想申请对方的资源所以这两个线程就会互相等待而进入死锁状态。 如下图所示,线程 A 持有资源 2线程 B 持有资源 1他们同时都想申请对方的资源所以这两个线程就会互相等待而进入死锁状态。
![线程死锁示意图 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-4/2019-4%E6%AD%BB%E9%94%811.png) ![线程死锁示意图 ](https://oss.javaguide.cn/github/javaguide/java/2019-4%E6%AD%BB%E9%94%811.png)
下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》) 下面通过一个例子来说明线程死锁,代码模拟了上图的死锁的情况 (代码来源于《并发编程之美》)

View File

@ -206,7 +206,7 @@ try {
在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger``LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 在 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger``LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。
![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png) ![JUC原子类概览](https://oss.javaguide.cn/github/javaguide/java/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88-20230814005211968.png)
```java ```java
// LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好 // LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好

View File

@ -583,7 +583,7 @@ public class CompletableFuture<T> implements Future<T>, CompletionStage<T> {
AQS 的全称为 `AbstractQueuedSynchronizer` ,翻译过来的意思就是抽象队列同步器。这个类在 `java.util.concurrent.locks` 包下面。 AQS 的全称为 `AbstractQueuedSynchronizer` ,翻译过来的意思就是抽象队列同步器。这个类在 `java.util.concurrent.locks` 包下面。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/AQS.png) ![](https://oss.javaguide.cn/github/javaguide/java/AQS.png)
AQS 就是一个抽象类,主要用来构建锁和同步器。 AQS 就是一个抽象类,主要用来构建锁和同步器。
@ -606,7 +606,7 @@ CLH 队列结构如下图所示:
AQS(`AbstractQueuedSynchronizer`)的核心原理图(图源[Java 并发之 AQS 详解](https://www.cnblogs.com/waterystone/p/4920797.html))如下: AQS(`AbstractQueuedSynchronizer`)的核心原理图(图源[Java 并发之 AQS 详解](https://www.cnblogs.com/waterystone/p/4920797.html))如下:
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/Java%20%E7%A8%8B%E5%BA%8F%E5%91%98%E5%BF%85%E5%A4%87%EF%BC%9A%E5%B9%B6%E5%8F%91%E7%9F%A5%E8%AF%86%E7%B3%BB%E7%BB%9F%E6%80%BB%E7%BB%93/CLH.png) ![](https://oss.javaguide.cn/github/javaguide/java/CLH.png)
AQS 使用 **int 成员变量 `state` 表示同步状态**,通过内置的 **线程等待队列** 来完成获取资源线程的排队工作。 AQS 使用 **int 成员变量 `state` 表示同步状态**,通过内置的 **线程等待队列** 来完成获取资源线程的排队工作。

View File

@ -39,7 +39,7 @@ try {
像 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger``LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。 像 Java 中`java.util.concurrent.atomic`包下面的原子变量类(比如`AtomicInteger``LongAdder`)就是使用了乐观锁的一种实现方式 **CAS** 实现的。
![JUC原子类概览](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/JUC原子类概览.png) ![JUC原子类概览](https://oss.javaguide.cn/github/javaguide/java/JUC%E5%8E%9F%E5%AD%90%E7%B1%BB%E6%A6%82%E8%A7%88-20230814005415437.png)
```java ```java
// LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好 // LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好

View File

@ -121,7 +121,7 @@ ClassFile {
类访问和属性修饰符: 类访问和属性修饰符:
![类访问和属性修饰符](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/访问标志.png) ![类访问和属性修饰符](https://oss.javaguide.cn/github/javaguide/java/%E8%AE%BF%E9%97%AE%E6%A0%87%E5%BF%97.png)
我们定义了一个 `Employee` 我们定义了一个 `Employee`
@ -134,7 +134,7 @@ public class Employee {
通过`javap -v class类名` 指令来看一下类的访问标志。 通过`javap -v class类名` 指令来看一下类的访问标志。
![查看类的访问标志](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/查看类的访问标志.png) ![查看类的访问标志](https://oss.javaguide.cn/github/javaguide/java/%E6%9F%A5%E7%9C%8B%E7%B1%BB%E7%9A%84%E8%AE%BF%E9%97%AE%E6%A0%87%E5%BF%97.png)
### 当前类This Class、父类Super Class、接口Interfaces索引集合 ### 当前类This Class、父类Super Class、接口Interfaces索引集合
@ -162,7 +162,7 @@ Java 类的继承关系由类索引、父类索引和接口索引集合三项确
**field info(字段表) 的结构:** **field info(字段表) 的结构:**
![字段表的结构 ](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/字段表的结构.png) ![字段表的结构 ](https://oss.javaguide.cn/github/javaguide/java/%E5%AD%97%E6%AE%B5%E8%A1%A8%E7%9A%84%E7%BB%93%E6%9E%84.png)
- **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化transient 修饰符),可变性final,可见性volatile 修饰符,是否强制从主内存读写)。 - **access_flags:** 字段的作用域(`public` ,`private`,`protected`修饰符),是实例变量还是类变量(`static`修饰符),可否被序列化transient 修饰符),可变性final,可见性volatile 修饰符,是否强制从主内存读写)。
- **name_index:** 对常量池的引用,表示的字段的名称; - **name_index:** 对常量池的引用,表示的字段的名称;
@ -189,7 +189,7 @@ Class 文件存储格式中对方法的描述与对字段的描述几乎采用
**method_info(方法表的) 结构:** **method_info(方法表的) 结构:**
![方法表的结构](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/方法表的结构.png) ![方法表的结构](https://oss.javaguide.cn/github/javaguide/java/%E6%96%B9%E6%B3%95%E8%A1%A8%E7%9A%84%E7%BB%93%E6%9E%84.png)
**方法表的 access_flag 取值:** **方法表的 access_flag 取值:**

View File

@ -85,7 +85,7 @@ tag:
**基本数据类型的零值**(图片来自《深入理解 Java 虚拟机》第 3 版 7.33 ) **基本数据类型的零值**(图片来自《深入理解 Java 虚拟机》第 3 版 7.33 )
![基本数据类型的零值](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-6/基本数据类型的零值.png) ![基本数据类型的零值](https://oss.javaguide.cn/github/javaguide/java/%E5%9F%BA%E6%9C%AC%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B%E7%9A%84%E9%9B%B6%E5%80%BC.png)
### 解析 ### 解析

View File

@ -96,7 +96,7 @@ List 类本身就有一个 `sort` 方法。并且 Java 编译器可以自动推
Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持 Lambda。最终采取的方法是增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口,可以被隐式转换为 lambda 表达式。`java.lang.Runnable``java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8 增加了一种特殊的注解`@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用`@FunctionalInterface` 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示 Java 语言设计者们投入了大量精力来思考如何使现有的函数友好地支持 Lambda。最终采取的方法是增加函数式接口的概念。**“函数式接口”是指仅仅只包含一个抽象方法,但是可以有多个非抽象方法(也就是上面提到的默认方法)的接口。** 像这样的接口,可以被隐式转换为 lambda 表达式。`java.lang.Runnable``java.util.concurrent.Callable` 是函数式接口最典型的两个例子。Java 8 增加了一种特殊的注解`@FunctionalInterface`,但是这个注解通常不是必须的(某些情况建议使用),只要接口只包含一个抽象方法,虚拟机会自动判断该接口为函数式接口。一般建议在接口上使用`@FunctionalInterface` 注解进行声明,这样的话,编译器如果发现你标注了这个注解的接口有多于一个抽象方法的时候会报错的,如下图所示
![@FunctionalInterface 注解](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-2/@FunctionalInterface.png) ![@FunctionalInterface 注解](https://oss.javaguide.cn/github/javaguide/java/@FunctionalInterface.png)
示例: 示例:

View File

@ -25,7 +25,7 @@ tag:
**如果需要通俗地描述容器的话,我觉得容器就是一个存放东西的地方,就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。** **如果需要通俗地描述容器的话,我觉得容器就是一个存放东西的地方,就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。**
![认识容器](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/container.png) ![认识容器](https://oss.javaguide.cn/github/javaguide/tools/docker/container.png)
### 1.2 图解物理机,虚拟机与容器 ### 1.2 图解物理机,虚拟机与容器
@ -33,11 +33,11 @@ tag:
**物理机:** **物理机:**
![物理机](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/物理机图解.png) ![物理机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E7%89%A9%E7%90%86%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)
**虚拟机:** **虚拟机:**
![虚拟机](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/虚拟机图解.png) ![虚拟机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)
**容器:** **容器:**
@ -123,7 +123,7 @@ tag:
理解了这三个概念,就理解了 Docker 的整个生命周期 理解了这三个概念,就理解了 Docker 的整个生命周期
![docker基本概念](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/docker基本概念.png) ![Docker 基本概念](https://oss.javaguide.cn/github/javaguide/tools/docker/docker%E5%9F%BA%E6%9C%AC%E6%A6%82%E5%BF%B5.jpeg)
### 4.1 镜像(Image):一个特殊的文件系统 ### 4.1 镜像(Image):一个特殊的文件系统
@ -165,7 +165,7 @@ Docker 设计时,就充分利用 **Union FS** 的技术,将其设计为**分
比如我们想要搜索自己想要的镜像: 比如我们想要搜索自己想要的镜像:
![利用Docker Hub 搜索镜像](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-7/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png) ![利用Docker Hub 搜索镜像](https://oss.javaguide.cn/github/javaguide/tools/docker/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png)
在 Docker Hub 的搜索结果中,有几项关键的信息有助于我们选择合适的镜像: 在 Docker Hub 的搜索结果中,有几项关键的信息有助于我们选择合适的镜像:

View File

@ -21,7 +21,7 @@ tag:
为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。 为了解决这个问题,人们很久以前就开发了许多种本地版本控制系统,大多都是采用某种简单的数据库来记录文件的历次更新差异。
![本地版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/本地版本控制系统.png) ![本地版本控制系统](https://oss.javaguide.cn/github/javaguide/tools/git/%E6%9C%AC%E5%9C%B0%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E7%B3%BB%E7%BB%9F.png)
### 集中化的版本控制系统 ### 集中化的版本控制系统
@ -29,7 +29,7 @@ tag:
集中化的版本控制系统都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。 集中化的版本控制系统都有一个单一的集中管理的服务器,保存所有文件的修订版本,而协同工作的人们都通过客户端连到这台服务器,取出最新的文件或者提交更新。
![集中化的版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/集中化的版本控制系统.png) ![集中化的版本控制系统](https://oss.javaguide.cn/github/javaguide/tools/git/%E9%9B%86%E4%B8%AD%E5%8C%96%E7%9A%84%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E7%B3%BB%E7%BB%9F.png)
这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题: 这么做虽然解决了本地版本控制系统无法让在不同系统上的开发者协同工作的诟病,但也还是存在下面的问题:
@ -42,7 +42,7 @@ tag:
这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。 这类系统,客户端并不只提取最新版本的文件快照,而是把代码仓库完整地镜像下来。 这么一来,任何一处协同工作用的服务器发生故障,事后都可以用任何一个镜像出来的本地仓库恢复。 因为每一次的克隆操作,实际上都是一次对代码仓库的完整备份。
![分布式版本控制系统](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3/分布式版本控制系统.png) ![分布式版本控制系统](https://oss.javaguide.cn/github/javaguide/tools/git/%E5%88%86%E5%B8%83%E5%BC%8F%E7%89%88%E6%9C%AC%E6%8E%A7%E5%88%B6%E7%B3%BB%E7%BB%9F.png)
分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。 分布式版本控制系统可以不用联网就可以工作,因为每个人的电脑上都是完整的版本库,当你修改了某个文件后,你只需要将自己的修改推送给别人就可以了。但是,在实际使用分布式版本控制系统的时候,很少会直接进行推送修改,而是使用一台充当“中央服务器”的东西。这个服务器的作用仅仅是用来方便“交换”大家的修改,没有它大家也一样干活,只是交换修改不方便而已。
@ -66,7 +66,7 @@ Git 在保存和对待各种信息的时候与其它版本控制系统有很大
具体原理如下图所示,理解起来其实很简单,每当我们提交更新一个文件之后,系统都会记录这个文件做了哪些更新,以增量符号 Δ(Delta)表示。 具体原理如下图所示,理解起来其实很简单,每当我们提交更新一个文件之后,系统都会记录这个文件做了哪些更新,以增量符号 Δ(Delta)表示。
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3deltas.png) ![](https://oss.javaguide.cn/github/javaguide/tools/git/2019-3deltas.png)
**我们怎样才能得到一个文件的最终版本呢?** **我们怎样才能得到一个文件的最终版本呢?**
@ -78,7 +78,7 @@ Git 在保存和对待各种信息的时候与其它版本控制系统有很大
Git 不按照以上方式对待或保存数据。 反之Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效如果文件没有修改Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 **快照流** Git 不按照以上方式对待或保存数据。 反之Git 更像是把数据看作是对小型文件系统的一组快照。 每次你提交更新,或在 Git 中保存项目状态时,它主要对当时的全部文件制作一个快照并保存这个快照的索引。 为了高效如果文件没有修改Git 不再重新存储该文件,而是只保留一个链接指向之前存储的文件。 Git 对待数据更像是一个 **快照流**
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3snapshots.png) ![](https://oss.javaguide.cn/github/javaguide/tools/git/2019-3snapshots.png)
### Git 的三种状态 ### Git 的三种状态
@ -90,7 +90,7 @@ Git 有三种状态,你的文件可能处于其中之一:
由此引入 Git 项目的三个工作区域的概念:**Git 仓库(.git directory)**、**工作目录(Working Directory)** 以及 **暂存区域(Staging Area)** 由此引入 Git 项目的三个工作区域的概念:**Git 仓库(.git directory)**、**工作目录(Working Directory)** 以及 **暂存区域(Staging Area)**
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3areas.png) ![](https://oss.javaguide.cn/github/javaguide/tools/git/2019-3areas.png)
**基本的 Git 工作流程如下:** **基本的 Git 工作流程如下:**
@ -200,7 +200,7 @@ git branch test
git checkout test git checkout test
``` ```
![](https://my-blog-to-use.oss-cn-beijing.aliyuncs.com/2019-3切换分支.png) ![](https://oss.javaguide.cn/github/javaguide/tools/git/2019-3%E5%88%87%E6%8D%A2%E5%88%86%E6%94%AF.png)
你也可以直接这样创建分支并切换过去(上面两条命令的合写) 你也可以直接这样创建分支并切换过去(上面两条命令的合写)