背景介绍

ThreadLocal可以为每个使用该变量的线程都保存一个独立的副本

Demo演示

public class ThreadLocalDemo {

    public static void main(String[] args) throws InterruptedException {
        final ThreadLocal<String> req = new ThreadLocal<String>();
        new Thread() {
            @Override
            public void run() {
                req.set("123");
                System.out.println("thread1:" + req.get());// thread1:123
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                req.set("456");
                System.out.println("thread2:" + req.get());// thread2:456
            }
        }.start();

        Thread.sleep(1000);
    }
}

应用场景

  1. ThreadLocal 用作保存每个线程独享的对象,为每个线程都创建一个副本,这样每个线程都可以修改自己所拥有的副本, 而不会影响其他线程的副本,确保了线程安全
  2. ThreadLocal 用作每个线程内需要独立保存信息,以便供其他方法更方便地获取该信息的场景。每个线程获取到的信息可能都是不一样的,前面执行的方法保存了信息后,后续方法可以通过ThreadLocal 直接获取到,避免了传参,类似于全局变量的概念

    原理分析

    调用流程

    在调用ThreadLocal的set方法时,Threadlocal会先获取当前线程,通过当前线程获取其中的成员变量(ThreadLocalMap类型,Thread类中的内部类)threadLocals,这个变量与map类似,也是通过key -> value的形式存储数据。最后ThreadLocalMap将当前ThreadLocal对象作为key,将set的值作为value进行存储
    截屏2020-12-16 下午11.37.45.png

    ThreadLocalMap中重要的变量

    ThreadLocalMap类似一个HashMap

    元素对象

    /**
    * The entries in this hash map extend WeakReference, using
    * its main ref field as the key (which is always a
    * ThreadLocal object).  Note that null keys (i.e. entry.get()
    * == null) mean that the key is no longer referenced, so the
    * entry can be expunged from table.  Such entries are referred to
    * as "stale entries" in the code that follows.
    */
    static class Entry extends WeakReference<ThreadLocal<?>> {
     /** The value associated with this ThreadLocal. */
     Object value;
    
     Entry(ThreadLocal<?> k, Object v) {
         super(k);
         value = v;
     }
    }
    

    初始化数组长度

    初始化长度为16

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

    扩容阈值

    当达到2/3时扩容

    /**
    * Set the resize threshold to maintain at worst a 2/3 load factor.
    */
    private void setThreshold(int len) {
     threshold = len * 2 / 3;
    }
    

    hash冲突解决方案

    HashMap中当发生hash冲突的时候是采用链表或红黑树解决,但是ThreadLocalMap是通过线性探测法解决。就是当发现数组中该下标元素有数据时,就寻找数组的下一个位置看是否有值

    /**
    * Construct a new map including all Inheritable ThreadLocals
    * from given parent map. Called only by createInheritedMap.
    *
    * @param parentMap the map associated with parent thread.
    */
    private ThreadLocalMap(ThreadLocalMap parentMap) {
     Entry[] parentTable = parentMap.table;
     int len = parentTable.length;
     setThreshold(len);
     table = new Entry[len];
    
     for (int j = 0; j < len; j++) {
         Entry e = parentTable[j];
         if (e != null) {
             @SuppressWarnings("unchecked")
             ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
             if (key != null) {
                 Object value = key.childValue(e.value);
                 Entry c = new Entry(key, value);
                 int h = key.threadLocalHashCode & (len - 1);
                 while (table[h] != null)
                     h = nextIndex(h, len);
                 table[h] = c;
                 size++;
             }
         }
     }
    }
    

    内存泄露问题

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

    在ThreadLocalMap中,key为弱引用,所以导致在gc时,可能会把key进行回收,而此时value还在(内存泄露)。JDK团队在set、get和remove时会对其中的key为null的数据进行扫描和清理