Skip to content

Commit 2776bc6

Browse files
MVCC学习
MVCC学习
1 parent 435fe1d commit 2776bc6

36 files changed

Lines changed: 381 additions & 38 deletions

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444

4545
**MySQL**
4646

47-
- [Mysql索引学习](/mysql进阶/Mysql学习一:索引.md)
47+
- [Mysql索引学习](/mysql进阶/Mysql学习一:索引.md) | [MVCC](/mysql进阶/MySQL MVCC.md)
4848

4949
**MongoDB**
5050

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
# Spring中自定义依赖注入对象注入Controller中,解决用户鉴权问题
2+
3+
通常我们在写Controller的时候对需要对调用参数的用户进行权限校验,并获取这个用户的信息,一般在用户登录之后的请求中都会携带一个token,我们会在Controller方法被调用前的拦截器中对用户进行鉴权,我们只需要实现`HandlerInterceptorAdapter`接口即可,并通过查缓存获取用户的信息,之后存到当前线程的对象中,通过`LocalThread`就可以拿到这个用户的信息,或者自己封装一个工具类,类似于:
4+
5+
```java
6+
/**
7+
* 获取自己信息
8+
*/
9+
@GetMapping({"/userInfo","/consumerInfomation"})
10+
public ResponseEntity<ChaUser> userInfo() {
11+
return ResponseEntity.ok(LocalThreadUtils.getObj());
12+
}
13+
```
14+
15+
这样的好处是把统一的鉴权逻辑在拦截器中处理完毕了,业务层只需要关心业务即可
16+
17+
但是这种方式不是很优雅,并且在实际开发中有的同学会在`Service`层或`dao`层也乱用`LocalThreadUtils.getObj()`的这个方法,这就导致如果在`controller`或者`Service`层开启了一个异步操作,或者是通过`RPC`的方式调用其他模块的接口,就会导致拿不到这个用户的信息,所以我们希望能过通过依赖注入的方式将当前调用这个接口的对象注入到`controller`中,类似于:
18+
19+
```java
20+
/**
21+
* 获取自己信息
22+
*/
23+
@GetMapping({"/userInfo", "/consumerInfomation"})
24+
public ResponseEntity<ChaUser> userInfo(@User ChaUser user) {
25+
return ResponseEntity.ok(user);
26+
}
27+
```
28+
29+
那这个怎么实现呢?其实也很简单,因为我们能想到的其实`Spring`都已经给我们提供好了,有一个接口`HandlerMethodArgumentResolver`,我们通常把它称为:`参数解析器`,我们只需要实现这个接口,就能拥有一个自己的依赖注入能力,再将我们自己的解析器交给Spring容器即可
30+
31+
我们先来看一下这个接口:
32+
33+
```java
34+
public interface HandlerMethodArgumentResolver {
35+
// 判断Controller层中的参数,是否满足条件,满足条件则执行resolveArgument方法,不满足则跳过
36+
boolean supportsParameter(MethodParameter var1);
37+
// 用户自定义业务逻辑,并返回自己需要注入的实例,其中webRequest就是本次请求的servlet对象
38+
Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception;
39+
}
40+
```
41+
42+
详细参数可以在源码的注释中看的非常清楚,所以现在我们只需要实现一个自己的`UserHandlerMethodArgumentResolver`,当然Spring默认提供了26种参数解析器,比如我们最常用的参数注解 `@RequestParam` 就是由 `RequestParamMethodArgumentResolver `解析的,`PathVariableMethodArgumentResolver `用来解析 `@PathVariable` 注解
43+
44+
我们先定义一个实体类`ChaUser`
45+
46+
```java
47+
@Data
48+
@Accessors(chain = true)
49+
public class ChaUser {
50+
private String userName;
51+
private Integer age;
52+
private String emailNumber;
53+
}
54+
```
55+
56+
定义一个注解`@User`,用来标记需要解析的bean:
57+
58+
```java
59+
/**
60+
* 用来标记当前对象应该从用户上下文中拿到,并注入到controller的方法参数中
61+
*
62+
* @author Eureka
63+
* @since 2022年9月22日19:43:07
64+
* @see ChaUser
65+
* @see HandlerMethodArgumentResolver
66+
* @see UserHandlerMethodArgumentResolver
67+
*/
68+
@Target(ElementType.PARAMETER)
69+
@Retention(RetentionPolicy.RUNTIME)
70+
@Documented
71+
public @interface User {
72+
}
73+
```
74+
75+
再定义我们自己的参数解析器`UserHandlerMethodArgumentResolver`
76+
77+
```java
78+
@Component
79+
public class UserHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
80+
@Override
81+
public boolean supportsParameter(MethodParameter parameter) {
82+
// 当参数上有@comment时才使用该解析器解析
83+
return parameter.hasParameterAnnotation(User.class);
84+
}
85+
86+
@Override
87+
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
88+
// 在这里注入当前用户的信息,webRequest中就可以取到用户的请求信息
89+
return new ChaUser().setUserName("张三").setAge(18).setEmailNumber("xxx@xxx.com");
90+
}
91+
}
92+
```
93+
94+
但是这样还不行,我们还需要将这个`参数解析器`加入到spring中,我们可以实现一个接口`WebMvcConfigurer#addArgumentResolvers`
95+
96+
```java
97+
@Configuration
98+
public class WebConfig implements WebMvcConfigurer {
99+
100+
@Autowired
101+
private UserHandlerMethodArgumentResolver userHandlerMethodArgumentResolver;
102+
103+
@Override
104+
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
105+
// 添加自己的拦截器
106+
resolvers.add(userHandlerMethodArgumentResolver);
107+
}
108+
}
109+
```
110+
111+
可以看到`List<HandlerMethodArgumentResolver>`里面存放的就是spring中所有的`参数解析器`了,我们只需要添加我们自己的就会在对应的容器生命周期过程中也处理我们自己的解析请求
112+
113+
我们写一个测试类测试一下
114+
115+
```java
116+
/**
117+
* 获取自己信息
118+
*/
119+
@GetMapping({"/userInfo", "/consumerInfomation"})
120+
public ResponseEntity<ChaUser> userInfo(@User ChaUser user) {
121+
return ResponseEntity.ok(user);
122+
}
123+
```
124+
125+
![image-20220925160647403](https://cdn.fengxianhub.top/resources-master/202209251606615.png)
126+
127+
可以说是非常的方便,这也是为什么Spring这么流行的原因,它留下了非常多的拓展点,只要我们能想到的,一般都会有对应的接口可以满足我们的需求

java/多线程学习/java中线程启动过程分析及本地方法 start0源代码的追踪学习.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
流程图:
66

7-
![image-20220311160938348](https://gitee.com/fengxian_duck/resources/raw/master/202203111609672.png)
7+
![image-20220311160938348](https://cdn.fengxianhub.top/resources-master/202203111609672.png)
88

99
>当我们调用Thread的start方法时(没有放任务,重写了run方法):
1010
@@ -217,7 +217,7 @@ vmSymbols::run_method_name()
217217

218218
我们可以在318查到:
219219

220-
![image-20220311194255458](https://gitee.com/fengxian_duck/resources/raw/master/202203111942665.png)
220+
![image-20220311194255458](https://cdn.fengxianhub.top/resources-master/202203111942665.png)
221221

222222
其实`vmSymbols::run_method_name()`就是调用了`run`方法
223223

java/多线程学习/多线程学习一、Java线程的处理方式.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
思维导图:
66

7-
![image-20220121105323377](https://gitee.com/fengxian_duck/resources/raw/master/202201211053456.png)
7+
![image-20220121105323377](https://cdn.fengxianhub.top/resources-master/202201211053456.png)
88

99

1010

@@ -132,7 +132,7 @@ public static void main(String[] args) {
132132

133133
通过源码分析可知,其实FutureTask 实现 Runnable接口底层还是调用了Callable接口,这体现了适配器设计模式,通过转换接口进行适配
134134

135-
![image-20220121104507280](https://gitee.com/fengxian_duck/resources/raw/master/202201211045456.png)
135+
![image-20220121104507280](https://cdn.fengxianhub.top/resources-master/202201211045456.png)
136136

137137

138138

java/多线程学习/多线程学习三,线程的分类.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@
22

33
思维导图:
44

5-
![image-20220121135520006](https://gitee.com/fengxian_duck/resources/raw/master/202201211355118.png)
5+
![image-20220121135520006](https://cdn.fengxianhub.top/resources-master/202201211355118.png)
66

77
## 1. 主线程
88

99
由<code>main</code>线程启动的就是<code>主线程</code>
1010

1111
那么在Java中一个类里只有一个线程吗?不是的,Java天生就是多线程的,即使在一个简单的Hello World程序中也会有很多线程的产生。
1212

13-
![image-20220127135310195](https://gitee.com/fengxian_duck/resources/raw/master/202201271353331.png)
13+
![image-20220127135310195](https://cdn.fengxianhub.top/resources-master/202201271353331.png)
1414

1515

1616

java/多线程学习/多线程学习二,线程生命周期和线程中断.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
思维导图:
88

9-
![image-20220121111254673](https://gitee.com/fengxian_duck/resources/raw/master/202201211112781.png)
9+
![image-20220121111254673](https://cdn.fengxianhub.top/resources-master/202201211112781.png)
1010

1111

1212

@@ -16,15 +16,15 @@
1616

1717
示意图:
1818

19-
![image-20220121111226777](https://gitee.com/fengxian_duck/resources/raw/master/202201211112900.png)
19+
![image-20220121111226777](https://cdn.fengxianhub.top/resources-master/202201211112900.png)
2020

2121

2222

2323
## 2. 线程中断
2424

2525
思维导图:
2626

27-
![image-20220121111534892](https://gitee.com/fengxian_duck/resources/raw/master/202201211115989.png)
27+
![image-20220121111534892](https://cdn.fengxianhub.top/resources-master/202201211115989.png)
2828

2929

3030

@@ -37,7 +37,7 @@
3737

3838
由此我们可以知道Java线程的设计是<code>协作式</code>的,而不是<code>抢占式</code>的,
3939

40-
![image-20220121111922631](https://gitee.com/fengxian_duck/resources/raw/master/202201211119738.png)
40+
![image-20220121111922631](https://cdn.fengxianhub.top/resources-master/202201211119738.png)
4141

4242

4343

@@ -105,11 +105,11 @@ public static void main(String[] args) {
105105

106106
jdk里<code>interrupt()</code>解释:
107107

108-
![image-20220121120938507](https://gitee.com/fengxian_duck/resources/raw/master/202201211209690.png)
108+
![image-20220121120938507](https://cdn.fengxianhub.top/resources-master/202201211209690.png)
109109

110110
当然 Java 也有很多底层实现是看不到的:
111111

112-
![image-20220121122201266](https://gitee.com/fengxian_duck/resources/raw/master/202201211222363.png)
112+
![image-20220121122201266](https://cdn.fengxianhub.top/resources-master/202201211222363.png)
113113

114114
### 2.3 interrupted检测线程中断并清除中断
115115

java/多线程学习/多线程学习五、volatile可见性与缓存不一致问题、指令重排序问题.md

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
思维导图:
88

9-
![image-20220128113453822](https://gitee.com/fengxian_duck/resources/raw/master/202201281135923.png)
9+
![image-20220128113453822](https://cdn.fengxianhub.top/resources-master/202201281135923.png)
1010

1111
>先通过一个小栗子来演示一下<code>volatile</code>的作用
1212
@@ -50,9 +50,9 @@ class Task implements Runnable{
5050

5151
🚩分析:首先线程在修改变量前,都会向主内存先申请这个变量,然后放到自己的内存地址,在修改了变量后会将这个变量重新刷回到主内存中去,但是此时其他线程中的变量的值已经确定了,这就造成了变量修改不了的情况
5252

53-
![image-20220128133049942](https://gitee.com/fengxian_duck/resources/raw/master/202201281330003.png)
53+
![image-20220128133049942](https://cdn.fengxianhub.top/resources-master/202201281330003.png)
5454

55-
![image-20220128132046122](https://gitee.com/fengxian_duck/resources/raw/master/202201281320185.png)
55+
![image-20220128132046122](https://cdn.fengxianhub.top/resources-master/202201281320185.png)
5656

5757
🚩解决方案:引入<code>volatile</code>关键字,给共享变量加上这个关键字。<code>volatile</code>关键字的作用是保证共享变量在多线程之间的可见性,能让每个线程实时感知到这个变量,加了这个关键字的变量,其他线程向主内存写数据时,会先检查该数据和之前取的是否一致,一致即写入,如果不一致会将主内存中的数据重新写入并进行处理,处理完再重复上述操作。这就保证了多线程下的数据一致性问题。
5858

@@ -66,7 +66,7 @@ volatile boolean isStop = false;
6666

6767
思维导图:
6868

69-
![image-20220128132600054](https://gitee.com/fengxian_duck/resources/raw/master/202201281326123.png)
69+
![image-20220128132600054](https://cdn.fengxianhub.top/resources-master/202201281326123.png)
7070

7171

7272

@@ -81,16 +81,16 @@ volatile boolean isStop = false;
8181
8282
**第一个问题解决措施**:在CPU里面设缓存,用来处理<code>I/O</code>操作速度不匹配的问题。
8383

84-
![image-20220128133810373](https://gitee.com/fengxian_duck/resources/raw/master/202201281338459.png)
84+
![image-20220128133810373](https://cdn.fengxianhub.top/resources-master/202201281338459.png)
8585

86-
>现在的CPU都是多核的,每个核心都会有自己的缓存,这些缓存可能还会分为多级,比如一级缓存L1、二级缓存L2、三级缓存L3,在这些缓存中,<code>一级和二级缓存是每个核心私有的</code>,而<code>三级缓存是所有核心所共有的</code>![image-20220128135711286](https://gitee.com/fengxian_duck/resources/raw/master/202201281357352.png)
86+
>现在的CPU都是多核的,每个核心都会有自己的缓存,这些缓存可能还会分为多级,比如一级缓存L1、二级缓存L2、三级缓存L3,在这些缓存中,<code>一级和二级缓存是每个核心私有的</code>,而<code>三级缓存是所有核心所共有的</code>![image-20220128135711286](https://cdn.fengxianhub.top/resources-master/202201281357352.png)
8787
8888
这里又有两个注意点:
8989

9090
- 缓存系统以<code>缓存行</code>(cache Line)为单位存储,它的存储空间是2的整数幂连续字节,一般在32-256个字节,常见的为64字节. CPU每次将一条内存指令所在的缓存行中的内容从主内存加载到cpu缓存中。
9191
- cpu只与<code>自己对应的缓存</code>发生作用,当cpu发生数据更新运算时,通过<code>直写</code>或<code>回写</code>将缓存中更新后的数据写到下一级缓存。
9292

93-
![image-20220128135153293](https://gitee.com/fengxian_duck/resources/raw/master/202201281351358.png)
93+
![image-20220128135153293](https://cdn.fengxianhub.top/resources-master/202201281351358.png)
9494

9595
读到这里我们好像能够窥探到上面栗子中一个线程修改另一个线程里面的变量失败的一些原因了,我们继续往下看。
9696

@@ -109,7 +109,7 @@ volatile boolean isStop = false;
109109

110110
思维导图:
111111

112-
![image-20220128151653675](https://gitee.com/fengxian_duck/resources/raw/master/202201281516907.png)
112+
![image-20220128151653675](https://cdn.fengxianhub.top/resources-master/202201281516907.png)
113113

114114
>解决方案一:通过在总线加<code>锁</code>来解决
115115
@@ -129,16 +129,18 @@ volatile boolean isStop = false;
129129
130130
🚩它的核心是: 当CPU写数据时,如果发现操作的变量是<code>共享变量</code>,即在其他<code>CPU中也存在该变量的副本</code>,会发出信号通知其他CPU将该变量的缓存行置为无效状态,因此当其他CPU需要读取这个变量时,发现自己缓存中缓存该变量的缓存行是无效的,那么它就会从内存重新读取
131131

132-
![image-20220128153714367](https://gitee.com/fengxian_duck/resources/raw/master/202201281537515.png)
132+
![image-20220128153714367](https://cdn.fengxianhub.top/resources-master/202201281537515.png)
133133

134134
>小结一下:<code>总线窥探技术</code>用来判断该数据是否被修改过,<code>缓存一致性协议</code>用来对修改过的数据进行处理
135135
136136
<hr>
137+
137138
### 2.2 指令重排序问题
138139

140+
139141
思维导图:
140142

141-
![image-20220128172216374](https://gitee.com/fengxian_duck/resources/raw/master/202201281722484.png)
143+
![image-20220128172216374](https://cdn.fengxianhub.top/resources-master/202201281722484.png)
142144

143145
​ 指令重排序是指通过对指令的重新排序来达到<code>提高运行性能</code>的作用。在<code>单线程情况下</code>,指令重排序并不会有影响,但在<code>多线程情况下</code>,指令重排序可能就会造成一些问题,产生的问题这里不过多赘述,我们重点讨论怎么解决这些问题。
144146

@@ -149,7 +151,7 @@ volatile boolean isStop = false;
149151

150152
操作系统提供了一些内存屏障以解决这种问题. 内存屏障是硬件层的技术,它分为两种: <code>Load Barrier </code>和 <code>Store Barrier</code>即<code>读屏障</code>和<code>写屏障</code>. 但不同的硬件提供的内存屏障不同。怎么办?我们的<code>JVM</code>也会提供屏障技术:
151153

152-
![image-20220128173523766](https://gitee.com/fengxian_duck/resources/raw/master/202201281735850.png)
154+
![image-20220128173523766](https://cdn.fengxianhub.top/resources-master/202201281735850.png)
153155

154156
通过这些屏蔽技术可以保证我们的指令不会被从新排序,从而保存多线程下数据的一致性
155157

@@ -168,11 +170,11 @@ volatile boolean isStop = false;
168170

169171
思维导图:
170172

171-
![image-20220128175145968](https://gitee.com/fengxian_duck/resources/raw/master/202201281751132.png)
173+
![image-20220128175145968](https://cdn.fengxianhub.top/resources-master/202201281751132.png)
172174

173175
原理图:
174176

175-
![image-20220128175604437](https://gitee.com/fengxian_duck/resources/raw/master/202201281756572.png)
177+
![image-20220128175604437](https://cdn.fengxianhub.top/resources-master/202201281756572.png)
176178

177179
>可以看到,Java内存模型和操作系统得内存模型具有惊人的相识之处
178180
@@ -200,7 +202,7 @@ public class _18_volatile {
200202

201203
图示一下这个过程:
202204

203-
![image-20220128180918419](https://gitee.com/fengxian_duck/resources/raw/master/202201281809599.png)
205+
![image-20220128180918419](https://cdn.fengxianhub.top/resources-master/202201281809599.png)
204206

205207
>但在多线程环境下,<code>t1</code>和<code>t2</code>都会把<code>i=0</code>这个变量先读到工作内存中,当<code>线程一</code>完成了<code>i++</code>的操作并将结果刷新到内存中时,其实<code>线程t2</code>的本地工作内存还没过期(已经读到之前i=0的数据了),那么它读到的数据就是<code>脏数据</code>了,<code>t2线程</code>处理完后又会将<code>i=1</code>再写到内存中,从而导致了问题
206208

0 commit comments

Comments
 (0)