JavaDriver JavaDriver
首页
  • 基础
  • 并发
  • JVM
  • 设计模式
  • 计算机网络
  • 操作系统
  • 数据结构
  • 算法
  • MYSQL
  • REDIS
  • Netty
  • Kafka
系统设计
非技术
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

YoungAnn

西二旗Java老司机一枚 致力于社会主义添砖Java
首页
  • 基础
  • 并发
  • JVM
  • 设计模式
  • 计算机网络
  • 操作系统
  • 数据结构
  • 算法
  • MYSQL
  • REDIS
  • Netty
  • Kafka
系统设计
非技术
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 基础

  • 并发

    • 线程池是如何实现的?
    • 简述 CAS 原理,什么是 ABA 问题,怎么解决?
    • 简述 Synchronized,Volatile,可重入锁的不同使用场景及优缺点
    • Synchronized 与 Lock 相比优缺点分别是什么?
    • 重入锁是如何实现的?
    • volatile 关键字解决了什么问题,它的实现原理是什么?
    • 简述 Java 锁升级的机制
    • 简述 Java AQS 的原理以及使用场景
    • 什么是公平锁?什么是非公平锁?
    • Java 的线程有哪些状态,转换关系是怎么样的?
    • Java 是如何实现线程安全的,哪些数据结构是线程安全的?
    • 手写死锁
    • 为什么我们不能直接调用 run() 方法?
    • Java 线程有哪些常用方法?
    • 手写生产者消费者模型
    • ThreadLocal 实现原理是什么?为什么要使用弱引用?
      • ThreadLocal 实现原理是什么?为什么要使用弱引用?
      • ThreadLocal解决什么问题?
      • ThreadLocal底层是什么数据结构?
      • ThreadLocal有什么应用场景?
      • 为什么要使用弱引用?
      • 子线程可以从父线程继承 ThreadLocal 吗?
      • ThreadLocal使用不当会有内存泄漏是怎么回事?
      • 使用 ThreadLocal 如何防止内存泄漏 ?
      • Netty 的FastThreadLocal fast在哪里?
      • FastThreadLocal 性能 比 ThreadLocal 高多少?
  • JVM

  • 设计模式

  • Java相关
  • 并发
YoungAnn
2022-03-11
目录

ThreadLocal 实现原理是什么?为什么要使用弱引用?

# ThreadLocal 实现原理是什么?为什么要使用弱引用?

ThreadLocal可以问的点有很多,比如:ThreadLocal解决什么问题?底层结构是什么?实现原理是什么?有什么应用场景?为什么要使用弱引用?子线程可以从父线程继承 ThreadLocal 吗?ThreadLocal使用不当会有内存泄漏是怎么回事?正确的使用姿势是?Netty 的FastThreadLocal fast在哪里?
下面我们一个个来看下这些问题。

# ThreadLocal解决什么问题?

通常情况下,我们创建的变量任何线程来读取读到的都是同一个值,如果想实现每一个线程都有自己的专属值该如何解决呢
?JDK中提供的ThreadLocal类正是为了解决这样的问题。
比如SimpleDateFormat 不是线程安全的,所以每个线程都要有自己独立的副本

# ThreadLocal底层是什么数据结构?

实际上是Map,Key为ThreadLocal变量、value为值。

我们先看下Thread类的源码

public class Thread implements Runnable {
 ......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

/*
 * InheritableThreadLocal,自父线程集成而来的ThreadLocalMap,
 * 主要用于父子线程间ThreadLocal变量的传递
 * 本文主要讨论的就是这个ThreadLocalMap
 */
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
 ......
}
1
2
3
4
5
6
7
8
9
10
11
12
13

我们可以把 ThreadLocal.ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap. 我们再来看下 ThreadLocal 类的源码

    //调用Thread.set 实际上是往当前线程的 ThreadLocalMap 里面put一个键值对
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    //调用Thread.getMap 可以获取到当前线程的 ThreadLocalMap
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    //ThreadLocalMap可以存储以ThreadLocal为key ,Object 对象为 value的键值对。
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        ......
    }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# ThreadLocal有什么应用场景?

  • 非线程安全的工具类,需要每个线程持有一个副本,比如 SimpleDateFormat
  • 异步传递traceId

# 为什么要使用弱引用?

ThreadLocalMap的内部类Entry被设计为实现了WeakReference,Entry用来存放数据。

弱引用简单理解就是当垃圾回收时,该对象只被WeakReference对象的弱引用字段所引用,而未被任何强类型的对象引用,那么,该弱引用的对象就会被回收。
注意:WeakReference引用本身是强引用,它内部的(T reference)才是真正的弱引用字段,WeakReference就是一个装弱引用的容器而已。
那 为什么要使用弱引用呢?
这是因为:ThreadLocalMap本身并没有为外界提供取出和存放数据的API,我们所能获得数据的方式只有通过ThreadLocal类提供的API来间接的从ThreadLocalMap取出数据,所以,当我们用不了key(ThreadLocal对象)的API也就无法从ThreadLocalMap里取出指定的数据。

一般我们new 一个ThreadLocal对象的时候,它一定会有强引用,在ThreadLocalMap中也一定会有它的弱引用
当强引用不在的时候一定是我们的程序不再需要这个ThreadLocal对象了 为什么这么说?
比如我定义了一个 ThreadLocal 变量 formatter,formatter 对 ThreadLocal 变量的强引用关系不存在的一个case 是getDate()方法执行完了,那么当然ThreadLocal 变量是可以回收的。

public void getDate(){

    ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
    ......
}

1
2
3
4
5
6

# 子线程可以从父线程继承 ThreadLocal 吗?

ThreadLocal 不可以。InheritableThreadLocals 是可以的,它重写了ThreadLocal的三个方法。childValue,createMap,getMap。

InheritableThreadLocal 不能和线程池搭配使用
因为线程池中的线程是复用的,并没有重新初始化线程,InheritableThreadLocal之所以起作用是因为在Thread类中最终会调用init()方法去把InheritableThreadLocal的map复制到子线程中。
由于线程池复用了已有线程,所以没有调用init()方法这个过程,也就不能将父线程中的InheritableThreadLocal值传给子线程。

# ThreadLocal使用不当会有内存泄漏是怎么回事?

ThreadLocalMap 中使用的 key 为 ThreadLocal 的弱引用,而 value 是强引用。
所以,如果 ThreadLocal 没有被外部强引用的情况下,在垃圾回收的时候,key 会被清理掉,而 value 不会被清理掉。
这样一来,ThreadLocalMap 中就会出现key为null的Entry。假如我们不做任何措施的话,value 永远无法被GC 回收,这个时候就可能会产生内存泄露。

# 使用 ThreadLocal 如何防止内存泄漏 ?

使用完 ThreadLocal 手动调用remove方法。 看下 ThreadLocal 的 remove() 会清理 ThreadLocalMap 中 key 为 null的键值对。

public void remove() {
    ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}
1
2
3
4
5

ThreadLocal 的 get() 有清除操作,那我们是不是不用手动调用 remove() 了?
不是的。因为 get() 中清除操作 只会检测本次get的 ThreadLocal 变量是否需要清理 。而 remove() 会检测 ThreadLocalMap 中的所有键值对。

所以,手动调用remove() 才是最保险的。

# Netty 的FastThreadLocal fast在哪里?

既然jdk已经有ThreadLocal,为何netty还要自己造个FastThreadLocal?FastThreadLocal快在哪里?
这需要从jdk ThreadLocal的本身说起。如下图:
在java线程中,每个线程都有一个ThreadLocalMap实例变量(如果不使用ThreadLocal,不会创建这个Map,一个线程第一次访问某个ThreadLocal变量时,才会创建)。该Map是使用线性探测的方式解决hash冲突的问题,如果没有找到空闲的slot,就不断往后尝试,直到找到一个空闲的位置,插入entry,这种方式在经常遇到hash冲突时,影响效率。

FastThreadLocal(下文简称ftl)直接使用数组避免了hash冲突的发生,具体做法是:每一个FastThreadLocal实例创建时,分配一个下标index;分配index使用AtomicInteger实现,每个FastThreadLocal都能获取到一个不重复的下标。当调用ftl.get()方法获取值时,直接从数组获取返回,如return array[index],如下图:

FastThreadLocal 底层结构代码

static final ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = new ThreadLocal<InternalThreadLocalMap>();
static final AtomicInteger nextIndex = new AtomicInteger();
Object[] indexedVariables;
1
2
3

# FastThreadLocal 性能 比 ThreadLocal 高多少?

远高于。
FastThreadLocal 对 ThreadLocal 的优化点在于,将元素放入 ThreadLocalMap 采用数组结构随机访问代替 原来的 线性探测。 所以我们测试场景为:单线程访问多 FastThreadLocal/ThreadLocal 变量:

/**
 * 单线程访问多个ThreadLocal
 */
public static void testThreadLocalWithMultipleThreadLocal() {
    ThreadLocal<String> threadLocal[] = new ThreadLocal[count];
    for (int i = 0; i < count; i++) {
        threadLocal[i] = new ThreadLocal<String>();
    }
    new Thread(new Runnable() {
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < count; i++) {
                threadLocal[i].set("value" + i);
            }
            long middle = System.currentTimeMillis();
            for (int i = 0; i < count; i++) {
                for (int j = 0; j < count; j++) {
                    threadLocal[i].get();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("testThreadLocalWithMultipleThreadLocal set:" + (middle - start) + ",get:" + (end - middle));
        }
    }).start();
}

/**
 * 单线程访问多个FastThreadLocal
 */
public static void testFastThreadLocalWithMultipleFastThreadLocal() {
    FastThreadLocal<String> threadLocal[] = new FastThreadLocal[count];
    for (int i = 0; i < count; i++) {
        threadLocal[i] = new FastThreadLocal<String>();
    }
    new FastThreadLocalThread(new Runnable() {
        @Override
        public void run() {
            long start = System.currentTimeMillis();
            for (int i = 0; i < count; i++) {
                threadLocal[i].set("value" + i);
            }
            long middle = System.currentTimeMillis();
            for (int i = 0; i < count; i++) {
                for (int j = 0; j < count; j++) {
                    threadLocal[i].get();
                }
            }
            long end = System.currentTimeMillis();
            System.out.println("testFastThreadLocalWithMultipleFastThreadLocal set:" + (middle - start) + ",get:" + (end - middle));
        }
    }).start();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

输出:

testThreadLocalWithMultipleThreadLocal set:68,get:21492
testFastThreadLocalWithMultipleFastThreadLocal set:61,get:8


1
2
3
4

有结果可知,FastThreadLocal 性能远高于 ThreadLocal。

编辑 (opens new window)
上次更新: 2022/05/19, 21:26:01
手写生产者消费者模型
Java 中垃圾回收机制中如何判断对象需要回收?

← 手写生产者消费者模型 Java 中垃圾回收机制中如何判断对象需要回收?→

最近更新
01
电商-商品系统设计
12-17
02
关于如何写OKR
12-09
03
对事不对人 vs 对人不对事
12-09
更多文章>
Theme by Vdoing | Copyright © 2022-2023 YoungAnnn | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式