ThreadLocal原理和实践

简介: ThreadLocal是线程本地变量,解决多线程环境下成员变量共享存在的问题。ThreadLocal为每个线程创建独立的的变量副本,他的特性是该变量的引用对全局可见,但是其值只对当前线程可用,每个线程都将自己的值保存到这个变量中而各线程不受影响。

一、概述

ThreadLocal是线程本地变量,解决多线程环境下成员变量共享存在的问题。ThreadLocal为每个线程创建独立的的变量副本,他的特性是该变量的引用对全局可见,但是其值只对当前线程可用,每个线程都将自己的值保存到这个变量中而各线程不受影响。

二、原理

1.原理说明

ThreadLocal的大致原理是这样的:

  1. 每个线程里面就有一个ThreadLocalMap的对象引用,ThreadLocalMap对象的数据结构是Entry组成的Map,其key值是ThreadLocal,而value值才是存放在该线程需要保存的value;
  2. 调用ThreadLocal的set()方法就往ThreadLocalMap里里面存值,value就是存入的值;
  3. 调用ThreadLocal的get()方法就是从ThreadLocalMap里面根据key值来取value;

ThreadLocal在JVM详细的逻辑图见下:

4A08ACC1-33F5-4834-83A1-A24ADDD35326

我的理解是ThreadLocal其实是为每一个线程创建一个副本变量,对于每个线程来说其变量引用相同,但其值不同。最简单的思路是创建一个Map,其key值是线程id,其值是各个线程的副本value,但是由于已经明确变量引用相同,所以该Map的key即为ThreadLocal本身,将其key和Value组合成一个实体Entry,放到其特定的ThreadLocalMap中,这就是ThreadLocal的原理。简而言之就是存放副本的数据结构ThreadLocalMap存在双头领导,一头已经释放,但另一头由于存活时间很长,所以很难释放对ThreadLocalMap的引用,从而导致ThreadLocalMap无法被及时GC。

2.代码分析

Thread中的成员变量threadLocals:

//Thread.java

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap的数据结构:

//ThreadLocal.ThreadLocalMap

static class ThreadLocalMap {
   
   
//实际保存数据的数据结构为Entry,其key为ThreadLocal的引用,value为各个线程需要保存的值
    static class Entry extends WeakReference> {
   
   
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal k, Object v) {
   
   
            super(k);
            value = v;
        }
    }

//成员变量
    /**
     * The initial capacity -- MUST be a power of two.
     */
    private static final int INITIAL_CAPACITY = 16;

    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
     */
    private Entry[] table;

    /**
     * The number of entries in the table.
     */
    private int size = 0;

    /**
     * The next size value at which to resize.
     */
    private int threshold; // Default to 0

    /**

    //省略...
    }

总的来说,ThreadLocalMap是保存多线程多副本的数据结构,Thread会保存ThreadLocalMap的引用,ThreadLocal也会保存ThreadLocalMap的引用。

三、实践

1.常用方法

ThreadLocal种最常见的呃是set()和get()方法,下面讲解下这两个方法;

  • set()方法

set()方法,顾名思义就是往ThreadLocal种塞值的,每个线程塞的值只对自己的线程可见;

  • get()方法

get()方法和set()方法对应,从ThreadLocal种取值出来;

  • initialValue()方法

设置ThreadLocal中变量的默认值;

  • remove()

移除该线程在ThreadLocal中的变量副本;

2.使用场景

ThreadLocal的使用场景包括多个线程要调用同一个资源,但该资源内部要为每一个线程分配不同资源的情况,比如数据库连接,在建立数据库连接时候,只需要建立一个datasource,通过这一个datasource可以为不同的请求建立connection连接;

3.代码实践

创建多个线程,分别验证通过set()方法设置值和get()获取值,并打印给ThreadLocal设置的默认值。

public class ThreadLocalTest {
   
   
    public static void main(String[] args) {
   
   
        ThreadLocal threadLocal = new ThreadLocal(){
   
   
            @Override
            protected Integer initialValue() {
   
   
                return 0;
            }
        };

        Thread t1 = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                threadLocal.set(1);
                try {
   
   
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });

        Thread t2 = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                threadLocal.set(2);
                try {
   
   
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });
        Thread t3 = new Thread(new Runnable() {
   
   
            @Override
            public void run() {
   
   
                try {
   
   
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
   
   
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + ":" + threadLocal.get());
            }
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

四、常见问题

1.碰到get()方法返回null的处理方法?

可以通过extends ThreadLocal并且重写initialValue()来实现初始化;

2.ThreadLocal内存泄露的解决方法?

从上面的逻辑图可以看到,ThreadLocalMap是在Thread里面的,所以ThreadLocalMap和Thread的生命周期是一样长的,但是ThreadLocalMap又被ThreadLocal引用,即使ThreadLocal已经使用完,但由于Thread没有将其引用释放,所以ThreadLocalMap还是不会被GC掉,这样很容易导致OOM,处理方法是线程在不用ThreadLocal的时候记得remove掉;


参考资料

  1. ThreadLocal就是这么简单:https://juejin.cn/post/6844903586984361992
  2. Java 并发 - ThreadLocal详解:https://pdai.tech/md/java/thread/java-thread-x-threadlocal.html
  3. 对ThreadLocal实现原理的一点思考:https://www.jianshu.com/p/ee8c9dccc953
  4. Java多线程编程-(8)-多图深入分析ThreadLocal原理:https://blog.csdn.net/xlgen157387/article/details/78297568
  5. 《Java多线程编程核心技术》
  6. 《码出高效java代码》
目录
相关文章
|
4月前
|
存储 安全 Java
ThreadLocal原理讲解
ThreadLocal原理讲解
25 0
|
5月前
|
存储 前端开发 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理1
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
193 0
|
5月前
|
存储 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理2
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
88 0
|
5月前
|
设计模式 缓存 Java
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理3
从ThreadLocal谈到TransmittableThreadLocal,从使用到原理
125 1
|
9月前
|
存储 Java
大厂是怎么用ThreadLocal?ThreadLocal核心原理分析
ThreadLocal**是Java中的一个线程本地变量类。它可以让每个线程都有自己独立的变量副本,而不会相互影响。
84 1
|
10月前
|
存储 SpringCloudAlibaba Java
浅析ThreadLocal使用及实现原理
提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其`get` 或 `set`方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。`ThreadLocal`实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联 。所以ThreadLocal与线程同步机制不同,线程同步机制是多个线程共享同一个变量,而ThreadLocal是为每一个线程创建一个单独的变量副本,故而每个线程都可以独立地改变自己所拥有的变量副本,而不会影响其他线程所对应的副本。可以这么说Th
78 0
浅析ThreadLocal使用及实现原理
|
安全 Java 数据库连接
深入浅出ThreadLocal
ThreadLocal相信大家都有用过的,一般用作存取一些全局的信息。比如用户信息,流程信息,甚至在Spring框架里面通过事务注解Transactional去获取数据库连接的实现上,也有它的一份功劳。
126 0
|
存储 安全 Java
|
存储 算法 安全
ThreadLocal原理剖析
ThreadLocal原理剖析
163 0
|
Java 定位技术
ThreadLocal原理
经典八股文之ThreadLocal原理
151 0