ThreadLocal的简单介绍

ThreadLocal内部实现与使用

什么是ThreadLocal?

在JDK1.8的官方文档中有这样的解释:

/**
 * This class provides thread-local variables.  These variables differ from
 * their normal counterparts in that each thread that accesses one (via its
 * {@code get} or {@code set} method) has its own, independently initialized
 * copy of the variable.  {@code ThreadLocal} instances are typically private
 * static fields in classes that wish to associate state with a thread (e.g.,
 * a user ID or Transaction ID).
 * 
 * <p>For example, the class below generates unique identifiers local to each
 * thread.
 * A thread's id is assigned the first time it invokes {@code ThreadId.get()}
 * and remains unchanged on subsequent calls.
 */  	

总的来说,ThreadLocal是一个用于创建线程局部变量的类。
一般情况下,我们创建的变量是对所有线程可见的,任何线程都能够访问并修改这个变量,那这在并发环境下就会出现线程安全的问题。而ThreadLocal变量能够使访问这个变量的每个线程都获得一个变量的本地副本,实现变量的线程私有化。

关键方法

首先我们创建一个ThreadLocal对象
	ThreadLocal<String> threadLocal = new ThreadLocal<>();

1.set()

然后在当前线程下使用set()方法设置一个String类型的值
	threadLocal.set("test");

set()方法内部是如何把这个变量归属给当前线程的呢?

	public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
	}

首先获得当前线程 t ,然后通过getMap()方法得到线程 t 的ThreadLocalMap对象,ThreadLocalMap又是什么东西呢?它是ThreadLocal的一个静态内部类,可以看作是为ThreadLocal定制的HashMap,其中Entry的定义如下:

	static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
    }

需要注意的是其中作为 key 的 ThreadLocal 对象是一个弱引用
接着再回到上面的 set() 方法源码,在 Thread 类中有一个初始值为 null 的 ThreadLocalMap 变量 threadlocals ,getMap() 方法返回的就是这个变量。如果 threadlocals 当前非空,说明 Map 已经被创建过,可以直接通过 ThreadLocalMap 的 set() 方法为当前线程set一个键值对,其中 key 为 ThreadLocal 对象,而Value在这里就是我们定义的 String 类型的变量 “ test ” 。如果为空,则为当前线程 t 的 threadlocals 变量新建 ThreadLocalMap 实例,同样地, key 为 ThreadLocal 对象,而Value在这里就是我们定义的 String 类型的变量 “ test ” 。

2.get()

先看一下源码中是如何定义 get() 方法的:
    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();
    }

同样地,get() 方法首先会通过 getMap() 方法获得当前线程的 threadlocals 变量,如果非空,则访问它的 Entry ,从而获得对应的 value 值。否则,返回一个初始化值,setInitialValue() 方法会为当前的 threadlocals 变量设置一个 key 为 ThreadLocal 对象,value 为 null 的键值对,并返回 null 值。

3.remove()

先上源码:
     public void remove() {
         ThreadLocalMap m = getMap(Thread.currentThread());
         if (m != null)
             m.remove(this);
     }

第一步同样也是要通过 getMap() 获取当前线程的 threadlocals 变量,如果非空,则调用ThreadLocalMap中的 remove() 方法移除当前的 ThreadLocal 对象。这里的 this 指针就是当前调用这个 remove() 方法的那个 ThreadLocal 对象。

使用实例

下面展示一个简单的Demo
public class Demo {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
    public static void main(String[] args) {
        for(int i = 0; i < 5; i++){
            String temp = String.valueOf(i);
            new Thread(() -> {
                String str = threadLocal.get();
                threadLocal.set(str + "+" + temp);
                System.out.printf("Thread name:%s , Value:%s, ThreadLocalHashCode:%s\n",
                Thread.currentThread().getName(),
                threadLocal.get(),
                threadLocal.hashCode());
            }).start();
        }
    }
}

控制台的输出结果:

Thread name:Thread-0 , Value:null+0, ThreadLocalHashCode:1516133987
Thread name:Thread-4 , Value:null+4, ThreadLocalHashCode:1516133987
Thread name:Thread-3 , Value:null+3, ThreadLocalHashCode:1516133987
Thread name:Thread-2 , Value:null+2, ThreadLocalHashCode:1516133987
Thread name:Thread-1 , Value:null+1, ThreadLocalHashCode:1516133987

从结果可以看出,不同的线程访问的是同一个 ThreadLocal 对象,它们通过 get() 方法得到的都是线程自己的 threadlocals 变量中的 value 值,如若不然,结果的 value 部分左侧不会一直是 null 值,而应该是上一个线程执行之后的结果。

线程与ThreadLocal对象的映射关系

内存泄漏

前面提到,在 ThreadLocalMap 的实现中,key 部分的 ThreadLocal 对象是一个弱引用,使用弱引用的原因是当没有强引用指向 ThreadLocal 变量时,它可以被回收,避免 ThreadLocal 对象不能被回收而造成内存泄漏。

然而,这又出现了一个新的内存泄漏问题,弱引用一旦被回收,而 ThreadLocalMap 中 Value 部分又是强引用,这会出现 key 为 null 的情况,导致整个 Entry 无法被回收。
针对这个问题,ThreadLocal类的源码中给出了解决方案:

  1. 在 ThreadLocalMap 的 set() 方法中调用了 replaceStaleEntry() 方法将所有 key 为 null 的 Entry 的值设置为 null,从而使得该值可被回收。
  2. 在 ThreadLocalMap 的 getEntry() 方法中调用了 getEntryAfterMiss() 方法,同样会把 key 为 null 的 Entry 的值设置为 null。
  3. 如果我们在使用之后不再去调用 set() 和 get() 方法,也就意味着还有可能会发生内存泄漏,因此在用完 ThreadLocal 之后最好手动调用 remove() 方法来回收内存,避免出现内存泄漏。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值