ThreadLocal原理与使用(笔记)

参考:ThreadLocal理解及应用

每一个 Thread 线程对象都会维护一个自己的 threadLocals 和从父线程继承(使用浅拷贝)来的 inheritableThreadLocals。这两个字段是无法访问的,且都是一个 ThreadLocal.ThreadLocalMap 类,ThreadLocal 为键,任意对象为值。每次 new ThreadLocal() 并调用 ThreadLocal.set(T) 时就会从当前线程取出 threadLocals (ThreadLocal.ThreadLocalMap 类) 并把 ThreadLocal 做为键,传入的泛型 T 作为值。

 public class Thread implements Runnable { 	
        ........
        
        /* ThreadLocal values pertaining to this thread. This map is maintained
         * by the ThreadLocal class. */
        ThreadLocal.ThreadLocalMap threadLocals = null; //本线程的 ThreadLocalMap
    
        /*
         * InheritableThreadLocal values pertaining to this thread. This map is
         * maintained by the InheritableThreadLocal class.
         */
        ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; //继承(浅拷贝)父线程的 ThreadLocalMap
        
        ........
}

以下是 Thread 执行的初始化逻辑:

 private void init(ThreadGroup g, Runnable target, String name,long stackSize,
                      AccessControlContext acc) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        // 当前线程就是该线程的父线程
        Thread parent = currentThread();
        this.group = g;
        // 将daemon、priority属性设置为父线程的对应属性
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        this.name = name.toCharArray();
        this.target = target;
        setPriority(priority);
        // 将父线程的InheritableThreadLocal复制过来(浅拷贝)
        // main线程的inheritableThreadLocals为null
        if (parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals=ThreadLocal.createInheritedMap(parent.
                                                                        inheritableThreadLocals);
        // 分配一个线程ID
        tid = nextThreadID();
}

1.ThreadLocalMap的存储结构

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

ThreadLocal.ThreadLocalMap 主要维护了 Entry[] 这个数组。Entry 是一个由(ThreadLocal,value)组成的二元组。

需要注意的是 Entry 继承了 WeakReference<ThreadLocal<?>> 弱引用,而这个弱引用指向的正是 ThreadLocal,通过 Entry 的构造方法可以看出:

 Entry(ThreadLocal<?> k, Object v) {
        super(k); // 调用 WeakReference 的构造,使弱引用指向 ThreadLocal
        value = v;
	}

因此,若没有其他引用指向这个 ThreadLocal,那么将在下一次 GC 时被回收,但是这个 ThreadLocal 对应的 value 却仍然在堆内存中,此时这个 Entry 为(null,vlaue),这就导致了 value 内存泄漏。

2.ThreadLocal的基本操作

由于 Thread 内部的 threadLocals(ThreadLocal.ThreadLocalMap 类)是私有的,并且没有提供任何操作它的方法,所以 ThreadLocal 就是操作 threadLocals 的媒介。本线程内调用的方法都可以通过 ThreadLocal 获取值。

通过 ThreadLocal 的 get 和 set 方法会从 threadLocals 里获取或设置其对应的 value:

    public void set(T value) {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value); //操作当前线程的 threadLocals
        else
            createMap(t, value);
    }
    
    public T get() {
        Thread t = Thread.currentThread(); //获取当前线程
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this); //操作当前线程的 threadLocals
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue(); //会调用initialValue(),这是一个空方法,子类可以重写,可以更方便的设置value
    }

通过匿名内部类重写 setInitialValue() 可以更方便地设置 value:

    public ThreadLocal<Long> threadId = new ThreadLocal<Long>() {
        @Override
        protected Long initialValue() {
            return 1L;
        }
    };

以上代码相当于:

 ThreadLocal<Long> threadId = new ThreadLocal<Long>();
    threadId.set(1L);

3.ThreadLocal是线程安全的

通过以上 1、2 点的描述可以知道,ThreadLocal 的 get 和 set 方法,都是通过操作当前线程中的 threadLocals 完成数据读取和设置的,而 Thread 类中 threadLocals 是私有的,并且没有提供任何操作的方法,因此对于 threadLocals 的任何操作,都是在本线程内完成,没有其他线程竞争资源。

4.正确使用ThreadLocal

由于 ThreadLocal.ThreadLocalMap 使用弱引用保存 ThreadLocal 对象,所以不恰当的使用 ThreadLocal 会造成内存溢出(上面第 1 点有说明)。使用 ThreadLocal 可以参考以下两个原则(选择其一即可):

  • ThreadLocal 申明为 private static final。
    • static 说明是类变量,只有程序结束才会回收,也就是说始终有一个引用链到达这个 ThreadLocal 。
    • final 说明这个引用不能被改变,如果改变就会导致这个 ThreadLocal 在内存中没有引用链到达。
    • ThreadLocal 由一个弱引用指向,只要 ThreadLocal 没有引用链到达,就会在下一次 GC 被回收,导致其 value 泄漏在内存中。
  • ThreadLocal使用后务必调用 remove方法。
    • remove 后 ThreadLocal 的 value 也可以被回收了,就不会泄漏了。

示例:

 public class ThreadLocalMemory {
        
        private static final ThreadLocal<Long> threadLocal = new ThreadLocal<Long>() {
            @Override
            protected Long initialValue() {
                return 100L;
            }
        };
    
        public Long get() {
            return threadLocal.get();
        }
    
        public void remove() {
            threadLocal.remove();
        }
        
    }
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值