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代码》

    本文由博客一文多发平台 OpenWrite 发布!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yangnk42

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值