mirror of
https://github.com/Snailclimb/JavaGuide
synced 2025-06-25 02:27:10 +08:00
Update 万字详解ThreadLocal关键字.md
This commit is contained in:
parent
2a4f95308d
commit
2e81334161
@ -23,8 +23,6 @@
|
||||
|
||||
### 目录
|
||||
|
||||
|
||||
|
||||
**注明:** 本文源码基于`JDK 1.8`
|
||||
|
||||
### `ThreadLocal`代码演示
|
||||
@ -91,7 +89,6 @@ size: 0
|
||||
- **弱引用**:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
|
||||
- **虚引用**:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知
|
||||
|
||||
|
||||
接着再来看下代码,我们使用反射的方式来看看`GC`后`ThreadLocal`中的数据情况:(下面代码来源自:https://blog.csdn.net/thewindkee/article/details/103726942 本地运行演示 GC 回收场景)
|
||||
|
||||
```java
|
||||
@ -140,6 +137,7 @@ public class ThreadLocalDemo {
|
||||
```
|
||||
|
||||
结果如下:
|
||||
|
||||
```java
|
||||
弱引用key:java.lang.ThreadLocal@433619b6,值:abc
|
||||
弱引用key:java.lang.ThreadLocal@418a15e3,值:java.lang.ref.SoftReference@bf97a12
|
||||
@ -247,11 +245,10 @@ public class ThreadLocal<T> {
|
||||
|
||||
`HashMap`中解决冲突的方法是在数组上构造一个**链表**结构,冲突的数据挂载到链表上,如果链表长度超过一定数量则会转化成**红黑树**。
|
||||
|
||||
而`ThreadLocalMap`中并没有链表结构,所以这里不能适用`HashMap`解决冲突的方式了。
|
||||
而 `ThreadLocalMap` 中并没有链表结构,所以这里不能使用 `HashMap` 解决冲突的方式了。
|
||||
|
||||

|
||||
|
||||
|
||||
如上图所示,如果我们插入一个`value=27`的数据,通过 `hash` 计算后应该落入第 4 个槽位中,而槽位 4 已经有了 `Entry` 数据。
|
||||
|
||||
此时就会线性向后查找,一直找到 `Entry` 为 `null` 的槽位才会停止查找,将当前元素放入此槽位中。当然迭代过程中还有其他的情况,比如遇到了 `Entry` 不为 `null` 且 `key` 值相等的情况,还有 `Entry` 中的 `key` 值为 `null` 的情况等等都会有不同的处理,后面会一一详细讲解。
|
||||
@ -308,10 +305,7 @@ public class ThreadLocal<T> {
|
||||
|
||||
从当前节点`staleSlot`向后查找`key`值相等的`Entry`元素,找到后更新`Entry`的值并交换`staleSlot`元素的位置(`staleSlot`位置为过期元素),更新`Entry`数据,然后开始进行过期`Entry`的清理工作,如下图所示:
|
||||
|
||||

|
||||
|
||||
|
||||
**向后遍历过程中,如果没有找到相同key值的Entry数据:**
|
||||
向后遍历过程中,如果没有找到相同 key 值的 Entry 数据:
|
||||
|
||||

|
||||
|
||||
@ -367,6 +361,7 @@ int i = key.threadLocalHashCode & (len-1);
|
||||
```
|
||||
|
||||
什么情况下桶才是可以使用的呢?
|
||||
|
||||
1. `k = key` 说明是替换操作,可以使用
|
||||
2. 碰到一个过期的桶,执行替换逻辑,占用过期桶
|
||||
3. 查找过程中,碰到桶中`Entry=null`的情况,直接使用
|
||||
@ -386,6 +381,7 @@ private static int prevIndex(int i, int len) {
|
||||
```
|
||||
|
||||
接着看剩下`for`循环中的逻辑:
|
||||
|
||||
1. 遍历当前`key`值对应的桶中`Entry`数据为空,这说明散列数组这里没有数据冲突,跳出`for`循环,直接`set`数据到对应的桶中
|
||||
2. 如果`key`值对应的桶中`Entry`数据不为空
|
||||
2.1 如果`k = key`,说明当前`set`操作是一个替换操作,做替换逻辑,直接返回
|
||||
@ -494,6 +490,7 @@ tab[staleSlot] = new Entry(key, value);
|
||||
```
|
||||
|
||||
最后判断除了`staleSlot`以外,还发现了其他过期的`slot`数据,就要开启清理数据的逻辑:
|
||||
|
||||
```java
|
||||
if (slotToExpunge != staleSlot)
|
||||
cleanSomeSlots(expungeStaleEntry(slotToExpunge), len);
|
||||
@ -724,10 +721,8 @@ private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `ThreadLocalMap`过期 key 的启发式清理流程
|
||||
|
||||
|
||||
上面多次提及到`ThreadLocalMap`过期可以的两种清理方式:**探测式清理(expungeStaleEntry())**、**启发式清理(cleanSomeSlots())**
|
||||
|
||||
探测式清理是以当前`Entry` 往后清理,遇到值为`null`则结束清理,属于**线性探测清理**。
|
||||
@ -816,7 +811,7 @@ private void init(ThreadGroup g, Runnable target, String name,
|
||||
|
||||
我们现在项目中日志记录用的是`ELK+Logstash`,最后在`Kibana`中进行展示和检索。
|
||||
|
||||
现在都是分布式系统统一对外提供服务,项目间调用的关系可以通过traceId来关联,但是不同项目之间如何传递`traceId`呢?
|
||||
现在都是分布式系统统一对外提供服务,项目间调用的关系可以通过 `traceId` 来关联,但是不同项目之间如何传递 `traceId` 呢?
|
||||
|
||||
这里我们使用 `org.slf4j.MDC` 来实现此功能,内部就是通过 `ThreadLocal` 来实现的,具体实现如下:
|
||||
|
||||
@ -833,6 +828,7 @@ private void init(ThreadGroup g, Runnable target, String name,
|
||||
#### Feign 远程调用解决方案
|
||||
|
||||
**服务发送请求:**
|
||||
|
||||
```java
|
||||
@Component
|
||||
@Slf4j
|
||||
@ -849,6 +845,7 @@ public class FeignInvokeInterceptor implements RequestInterceptor {
|
||||
```
|
||||
|
||||
**服务接收请求:**
|
||||
|
||||
```java
|
||||
@Slf4j
|
||||
@Component
|
||||
@ -906,8 +903,3 @@ public class MyThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {
|
||||
#### 使用 MQ 发送消息给第三方系统
|
||||
|
||||
在 MQ 发送的消息体中自定义属性`requestId`,接收方消费消息后,自己解析`requestId`使用即可。
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user