ThreadLocal

ThreadLocal

ThreadLocal是什么

ThreadLocal叫做本地线程变量,ThreadLocal中填充的是当前线程的变量,该变量对于其他线程而言是封闭且隔离的,ThreadLocal为变量在每个线程中创建一个副本,这样每个线程都可以访问自己内部的副本变量。

ThreadLocal与普通变量的区别

每个使用该变量的线程都会初始化一个完全独立的实例副本。ThreadLocal变量通常被private static修饰。当 一个线程结束时,他所使用的所有ThreadLocal相对的实例副本都可被回收。

ThreadLocal适用于每个线程需要自己独立的实例且该实例需要在多个方法中被使用,也即线程间隔离而在方法或类间共享的场景

ThreadLocal使用场景

  1. 在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层级之间的约束。

  2. 线程间的数据隔离

  3. 进行事务操作,用于存储线程信息。

  4. 数据库连接,Sesssion会话管理。

ThreadLocal与Synchronized的区别

ThreadLocal<T>其实是与线程绑定的一个变量。ThreadLocal和Synchronized都用于解决多线程并发访问。

区别:

  1. Synchronized用于线程间数据共享,而ThreadLocal则用于线程间数据隔离

  2. Synchronized是利用锁的机制,使变量或代码块在某一时刻只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得某个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程间的数据共享,而Synchronized恰好相反,它用于在多个线程间通信时能够获取数据共享

ThreadLocal是作为当前线程中属性ThreadLocalMap集合中的某一个Entry的key值Entry(threadLocal, value), 虽然不同线程间ThreadLocal这个key值是一样的,但是不同线程所拥有的ThreadLocalMap是独一无二的,也就是不同线程间同一个ThreadLocal(key)对应存储的值(value)不一样,从而达到了线程变量隔离的目的,但是在同一个线程中这个value变量地址是一样的

ThreadLocal使用方法

public class ThreadLocalTest1{
    public static void main(String[] args){
         ThreadLocal<String> local = new ThreadLocal<>();
        IntStream.range(0, 10).forEach(i -> new Thread(() -> {
            local.set(Thread.currentThread().getName() + ":" + i);
            System.out.println("线程:" + Thread.currentThread().getName() + ",local:" + local.get());
        }).start());
    }
}
​
输出结果:
线程:Thread-0,local:Thread-0:0
线程:Thread-1,local:Thread-1:1
线程:Thread-2,local:Thread-2:2
线程:Thread-3,local:Thread-3:3
线程:Thread-4,local:Thread-4:4
线程:Thread-5,local:Thread-5:5
线程:Thread-6,local:Thread-6:6
线程:Thread-7,local:Thread-7:7
线程:Thread-8,local:Thread-8:8
线程:Thread-9,local:Thread-9:9

源码分析

  1. set方法

/**
    *ThreadLocalMap为ThreadLocal的一个静态内部类,里面定义了Entry来保存数据。而且是继承的弱引用。在Entry内部使用            *ThreadLocal作为key,使用我们设置的value作为value
    *对于每个线程内部有个ThreadLocal.ThreadLocalMap变量,存取值的时候,也是从这个容器中来获取
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        //首先获取当前线程对象
        Thread t = Thread.currentThread();
        //获取线程中变量 ThreadLocal.ThreadLocalMap
        ThreadLocalMap map = getMap(t);
        //如果不为空,
        if (map != null)
            map.set(this, value);
        else
            //如果为空,初始化该线程对象的map变量,其中key 为当前的threadlocal 变量
            createMap(t, value);
    }
​
    /**
     * Create the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param t the current thread
     * @param firstValue value for the initial entry of the map
     */
//初始化线程内部变量 threadLocals ,key 为当前 threadlocal
    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }
​
       /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        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);
        }
​
​
 static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;
​
            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }
  1. get方法

/**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

ThreadLocal内存泄漏问题

/**
         * 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) 
         *如果key ThreadLocal为null了,这个entry就可以清除了。
         *ThreadLocal是一个弱引用,若为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;
            }
        }

如果ThreadLocal为null,即被垃圾回收器回收了,但ThreadLocalMap(Thread的内部属性)声明周期和Thread一样,并不会被回收。此时ThreadLocalMap的key没了,但是value还在,这就造成了内存泄漏

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

所以如同lock的操作最后要进行解锁操作一样,ThreadLocal使用完毕后一定记得执行remove方法,清除当前线程的数值。

如不remove当前线程的对应的VALUE,就会一直存在这个值。

使用了线程池,可以达到“线程复用”的效果。但是归还线程之前记得清除ThreadLocalMap,否则再去除该线程的时候,ThreadLocal变量还会存在。此时不只是内存泄露的问题了,整个业务逻辑都可能会出错。

为什么key使用弱引用

如果使用强引用,当ThreadLocal对象的引用(强引用)被回收了,ThreadLocalMap本身依然持有ThreadLocal的强引用,如果没有手动删除这个key,则ThreadLocal不会被回收,所以只要当前线程不消亡,ThreadLocalMap引用的那些对象就不会被回收,可以认为这导致Entry内存泄漏。

  • 强引用:普通的引用,强引用指向的对象不会被回收;

  • 软引用:仅有软引用指向的对象,只有发生gc且内存不足,才会被回收

  • 弱引用:仅有弱引用指向的对象,只要发生gc就会被回收

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值