ThreadLocal

Posted 有且仅有

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal相关的知识,希望对你有一定的参考价值。

我们知道线程也是一个「对象」,当线程这种对象想为我们提供一个「可以存取我们自定义变量的功能时」,来看下它是怎么做的。

一、Thread 中

  1. Thread增加一个成员变量java.lang.Thread#threadLocals,并让我们间接操作它(它是一个自定义静态内部类static class ThreadLocalMap):

    public class Thread implements Runnable 
        /* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null;
        // ...
    

    简单分析:

    • null : 代表懒加载,只在你需要存数据时才会实例化一个ThreadLocalMap对象。
    • default : 默认访问修饰符,代表只想让java.lang下被访问,就是给java.lang.ThreadLocal用。
  2. 为什么不在Thread里面给我们暴露方法?

    这不符合面向对象思想。所以要把这个功能「封装」为一个类,称为线程本地变量java.lang.ThreadLocal,每一个你需要的变量就是一个此类的实例。

二、ThreadLocal 实现代码

几个用到的类关系是:Thread 拥有一个 ThreadLocalMapThreadLocalMap拥有多个EntryEntry拥有一个ThreadLocal

  1. 既然用Map存,那EntryKeyValue分别是什么?

    • Key : 线程变量对象 ThreadLocal
    • Value : 你自定义操作的那个数据 Object
  2. 当然,这个静态内部类Map肯定对使用者透明

    所以我们不能直接操作这个Map,而是使用的ThreadLocal的三个public方法:get()/set()/remove()

    set 方法

    public void set(T value) 
        // 获取当前线程
        Thread t = Thread.currentThread();
        // 简单的返回Thread 对象的成员变量threadLocals
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    

    get 方法

    public T get() 
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) 
            // 这个Map 没有get方法只有getEntry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                // 你之前设置的值
                return (T)e.value;
        
        return setInitialValue();
    

    remove 方法

     public void remove() 
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     
    
    /**
     * Remove the entry for key.
     */
    private void remove(ThreadLocal<?> key) 
        Entry[] tab = table;
        int len = tab.length;
        int i = key.threadLocalHashCode & (len-1);
        for (Entry e = tab[i];
             e != null;
             e = tab[i = nextIndex(i, len)]) 
            if (e.get() == key) 
                e.clear(); // 清除对ThreadLocal的引用
                expungeStaleEntry(i); // 删除数组中的Entry
                return;
            
        
    

三、分析ThreadLocalMap

实线 + 燕尾箭头:代表强引用,虚线 + 燕尾箭头:代表弱引用

  1. key为何为弱引用?而不是强引用?

    设计者希望:当ThreadLocal没有强引用时,GC将随时有权收回其所占内存。

    如果是强引用,将导致用户已不再强引用ThreadLocal时,其被Entry强引用而不能释放。

  2. value为何“不是”弱引用?

    难道能发生:通过keyget valuevalue却被GC回收了这种事?

  3. 内存泄漏?

    ThreadLocal因没有强引用被回收后,value将处于内存泄漏状态。

    直到:

    • 线程死亡。
    • 对同一个线程上的另一个ThreadLocal对象get/set操作,可能会移除那些为key == nullEntry
  4. 如何避免内存泄漏?

    • 使用完TheradLocal要手动调用remove()
    • 尤其要注意使用线程池的场景,线程可能永远不死,更加要注意调用remove()
  5. 回顾下对象“可达性”

    1. 强可达对象:不需要遍历任意“引用对象”就在对象图上可达的对象是强可达对象。
    2. 软可达对象:不是强可达对象,遍历软引用可达的对象,是软可达对象。
    3. 弱可达对象:不是强可达和软可达对象,遍历弱引用可达的对象,是弱可达对象。
    4. 虚可达对象:不是强可达、软可达和弱可达对象,其finalize()方法已被执行,被一个虚引用引用的对象,是虚可达对象。
    5. 不可达对象:当不能以上述任何方法到达某一对象时,该对象是不可达对象,因此可以回收此对象。

四、ThreaLocal 使用

  1. Example

    JavaDoc 中已经给出了,摘抄一下:

    import java.util.concurrent.atomic.AtomicInteger;
    
    public class ThreadId 
        // Atomic integer containing the next thread ID to be assigned
        private static final AtomicInteger nextId = new AtomicInteger(0);
    
        // Thread local variable containing each thread's ID
        private static final ThreadLocal<Integer> threadId =
            new ThreadLocal<Integer>() 
                @Override protected Integer initialValue() 
                    return nextId.getAndIncrement();
            
        ;
    
        // Returns the current thread's unique ID, assigning it if necessary
        public static int get() 
            return threadId.get();
        
    
  2. 解释:初始化和set()方法

    首先,之所以要使用「匿名内部类」及其对象,是因为想让你在一行代码内就完成初始化。除此之外,initialValue()set()的实现代码区别不大。

    然后,一旦哪个Thread引用到这个ThreadLocal对象了,就会在自己的threadLocals中被设置一个值。

  3. get()就是从当前线程的Map中取即可。

以上是关于ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal为什么要使用弱引用和内存泄露问题

ThreadLocal为什么要使用弱引用和内存泄露问题

ThreadLocal父子线程数据传递解决方案

UML 关系 - 虚线与实线

UML关系 - 虚线与实线

ThreadLocal 和内存泄漏