ThreadLocal的一些总结

161 阅读3分钟

ThreadLocal能干什么?

ThreadLocal是当前线程的副本,一般用于数据隔离,填充的数据只属于当前线程,对别的线程不可见

ThreadLocal的原理?

  1. 如何使用?
public class ThreadLocalTest2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> threadLocal = new ThreadLocal<>();
        threadLocal.set("wanghongwei");
        threadLocal.get();
        threadLocal.remove();
    }
}
  1. set()源码:
 public void set(T value) {
        Thread t = Thread.currentThread(); // 获取当前线程 
        ThreadLocalMap map = getMap(t); // 获取线程的ThreadLocalMap对象
        if (map != null) // 校验对象是否为空
            map.set(this, value); //不为空set
        else
            createMap(t, value); // 为空创建一个map对象
    }
  1. getMap(t)的实现
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

可以看到ThreadLocalMap是当前线程Thread的一个变量threadLocals

public class Thread implements Runnable {

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

    /*
     * InheritableThreadLocal values pertaining to this thread. This map is
     * maintained by the InheritableThreadLocal class.
     */
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

到这就可以明白线程间数据隔离是怎么实现的了,每个线程维护了一个ThreadLocalMap对象,再set(value)时,实际上是在threadLocals变量中,别的线程无法获取到,从而实现了线程间的数据隔离

ThreadLocalMap的底层结构

    static class ThreadLocalMap {

        /**
         * 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;
            }
        }

        /**
         * The initial capacity -- MUST be a power of two.
         */
        private static final int INITIAL_CAPACITY = 16; // table的初始化的大小,必须是2的n次方

        /**
         * 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 

image.png 变量table是存放数据的主体

table为什么是数组结构?

一个线程Thread可以有多个ThreadLocal,存放不同类型的数据,他们都需要存放在当前线程的ThreadLocalMap中,所以需要数组存放,类似这种

public class ThreadLocalTest2 {
    public static void main(String[] args) throws InterruptedException {
        ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
        ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
        threadLocal1.set("hello world");
        System.out.println("ThreadName: " + Thread.currentThread().getName() + ", 值:" +threadLocal1.get() );
        threadLocal1.remove();
        
        threadLocal2.set(10);
        System.out.println("ThreadName: " + Thread.currentThread().getName() + ", 值:" +threadLocal2.get() );
        threadLocal2.remove();
    }
}

如何解决hash冲突问题

  1. 使用了开放寻址法:一旦发生冲突,就去寻找下一个空的散列地址
        private void set(ThreadLocal<?> key, Object value) {

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1); //定位到table中的位置

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) { // 刷新entry中的value
                    e.value = value;
                    return;
                }

                if (k == null) { // key为null
                //被GC回收的话,就需要替换过期的值,把新的值放到这里
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash(); //扩容
        }

threadLocal为什么使用弱引用

内存原理图

  下图是程序运行中的内存分布图,简要介绍一下这种图:当前线程有一个threadLocals属性(ThreadLocalMap属性),该map的底层是数组,每个数组元素时Entry类型,Entry类型的key是ThreadLocal类型(也就是创建的ThreadLocal对象),而value是则是ThreadLocal.set()方法设置的value。 image.png

  1. 假设threadLocal是强引用,也就是连线5是强引用,即使栈内存中ThreadLocal的引用被清楚,但是因为连线5是强引用,所以堆中ThreadLocal对象也不会被清除。如果当前的线程不结束,那么堆中ThreadLocal对象会一直存在
  2. 假设Entry是弱引用,此时当栈内存中ThreadLocal的引用被清除后,因为连线5是弱引用,所以当发生gc时,堆内存中的ThreadLocal对象会被回收,再加上ThreadLocalMap的每次get、set、remove,都会清理key=null的Entry,所以使用弱引用能有效的防止内存泄漏

ThreadLocal发生内存泄漏的原因

-- 前提条件:没有进行手动的remove()

  1. ThreadLocal是静态变量,外部的强引用一直存在,使用过之后,没有进行remove()
  2. 使用了线程池,当线程执行完之后,会被放回线程池,value的外部强引用还在,无法被回收 解决办法:使用完之后,调用remove()方法

如何实现ThreadLocal数据被多个线程共享

使用InheritableThreadLocal可以实现多个线程访问ThreadLocal的值

    public static void main(String[] args) {
        ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set("测试1");

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("threadlocal的值:" + threadLocal.get());
            }
        });
        thread.start();
    }

数据如何传递的?

public
class Thread implements Runnable{
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        ......
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        ......
    }
}

如果线程的inheritableThreadLocals为true,并且父线程的inheritableThreadLocals存在,那么就把父线程的inheritableThreadLocals传递给子线程

#参考文档

www.cnblogs.com/-beyond/p/1… juejin.cn/post/685457…