zoukankan      html  css  js  c++  java
  • ThreadLocal原理分析

    ThreadLocal原理分析

    ​ 通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。如果想实现每一个线程都有自己的专属本地变量该如何解决呢? JDK 中提供的ThreadLocal类正是为了解决这样的问题。 ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。

    如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的本地副本,这也是ThreadLocal变量名的由来。他们可以使用 get()set() 方法来获取默认值或将其值更改为当前线程所存的副本的值,从而避免了线程安全问题。

    原理

    //与此线程有关的ThreadLocal值。这个map由ThreadLocal类维护
    ThreadLocal.ThreadLocalMap threadLocals = null;
    
    //与此线程有关的InheritableThreadLocal值。这个map由由InheritableThreadLocal类维护
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
    

    ​ 从上面Thread类源代码可以看出Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,ThreadLocalMap是ThreadLocal的一个静态内部类,可以理解为是一个自定义的哈希映射(定制化的HashMap),仅适用于维护线程局部值。ThreadLocalMap的底层存储是一个Entry类型的数组,初始容量为16,每次扩容时,容量变为原来的两倍。

    ​ 默认情况下threadLocals和inheritableThreadLocals这两个变量都是null,只有当前线程调用 ThreadLocal 类的 setget方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()set()方法。

    ThreadLocal类的set方法:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
    
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }
    
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
    
    // ThreadLocalMap的初始容量大小,必须为2的幂
    private static final int INITIAL_CAPACITY = 16;
    
    //Construct a new map initially containing (firstKey, firstValue).
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
        table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
    

    ​ 通过上面的源码可以看出来,ThreadLocal类自身并不存储和管理任何数据,它只是数据和ThreadLocalMap之间的桥梁,数据最终存储的位置是在线程Thread对象的ThreadLocalMap中,一开始我们看Thread类的源码可以知道,每一个线程对象都有一个ThreadLocalMap实例。

    ​ ThrealLocal类中可以通过Thread.currentThread()获取到当前线程对象(调用ThrealLocal对象set方法的线程对象)后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

    每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue)
    

    ​ 存储结构示意图如下:

    ThreadLocal 内存泄露问题

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

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;
    
        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    

    解决办法:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况。

    参考:https://snailclimb.gitee.io/javaguide/#/docs/java/multi-thread/2020最新Java并发进阶常见面试题总结?id=_2-volatile-关键字

  • 相关阅读:
    为什么 PHP 程序员应该学习使用 Swoole
    如何优雅的使用和理解线程池
    Redis 数据结构-字符串源码分析
    MySQL多版本并发控制机制(MVCC)-源码浅析
    Spring事务用法示例与实现原理
    J2Cache 和普通缓存框架有何不同,它解决了什么问题?
    Spring Aop之Cglib实现原理详解
    Python中字符串拼接的N种方法
    使用Fiddler抓取到的“姐夫酷”API接口
    [Android]Space控件的应用场景
  • 原文地址:https://www.cnblogs.com/yxym2016/p/14558982.html
Copyright © 2011-2022 走看看