本文共 4642 字,大约阅读时间需要 15 分钟。
java.lang.ThreadLocal实现原理和源码分析
1、ThreadLocal的原理:为每一个线程维护变量的副本。某个线程修改的只是自己的副本。
2、ThreadLocal是如何做到把变量变成副本的呢?不是clone,而是简单地new。具体一点就是对于变量A,每一个线程都会执行一个初始化方法initialValue(),这个方法在ThreadLocal类的源码中只是返回null(下面代码),需要用户覆写,也就是在这个方法里面new一个A并返回,这个就是我们当前线程的副本。具体分析如下:
protected T initialValue() { return null;}
我们看一个例子:我们顺着例子中的main函数的执行过程来作为我们分析的思路
import java.util.ArrayList;import java.util.List;/** * Created by liangyh on 2/25/16. */public class ThreadLocalTest { private ThreadLocal
> threadLocal = new ThreadLocal
>(){ @Override protected List initialValue() { List list = new ArrayList<>(); list.add("a"); return list; } }; public String get(int index){ return threadLocal.get().get(index); } public void add(String value){ threadLocal.get().add(value); } public synchronized void print(){ List list = threadLocal.get(); if(list != null){ for (int i = 0; i
1、我们先实例化一个ThreadLocalTest对象,然后调用print()方法。在print()方法中第一行调用了threadLocal.get()方法。返回的是当前线程的副本,现在的线程是主线程。get()方法干了什么工作了?
先看get方法的源码!它首先获得当前线程的对象t,然后根据t作为参数得到一个map,这个map我们下面会讲到,它的key是ThreadLocal对象,value是变量副本。如果得到的map不为空,则返回map中的value值,如果为空,进入setInitialValue()方法中。在这个get方法中我们还应该注意到一个方法,getMap(Threadt),它返回的是Thread对象的一个属性ThreadLocal.ThreadLocalMapthreadLocals =null;这个属性的作用是保存这个线程的变量副本的。ThreadLocalMap便是上面提到的map。刚开始的时候,这个threadLocals变量是空的,什么时候赋值呢?暂时不管,先把思路回到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) return (T)e.value; } return setInitialValue(); }/** * Get the map associated with a ThreadLocal. Overridden in * InheritableThreadLocal. * * @param t the current thread * @return the map */ ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
程序执行到这里我们还没有拿到我们的值,下面这个方法的第一行代码便执行我们将要覆写的initialValue()方法,它会返回我们所需要的变量副本(可以看看上面的例子,在initialValue方法中我new了一个ArrayList对象并返回)。拿到我们的变量副本之后,如果当前线程的threadLocals变量不为空,则添加到其中,如果为空,需要创建一个ThreadLocalMap,也就是会执行setInitialValue方法中的createMap(t,value)方法。
/** * Variant of set() to establish initialValue. Used instead * of set() in case user has overridden the set() method. * * @return the initial value */ private T setInitialValue() { T value = initialValue(); Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); return value; }
下面方法的作用是把new一个ThreadLocalMap,并给当前线程也就是Thread对象的threadLocals变量赋值。这样之后,我们的变量副本便保存在了ThreadLocalMap中了,同时当前线程Thread的对象的threadLocals变量又保存了这个map,所以,我们的变量副本就和当前线程挂钩了。
/** * 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 * @param map the map to store. */ void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); }下面的是ThreadLocalMap静态内部类的构造方法。我们可以看到key为ThreadLocal类型,value是我们的变量副本。
/** * 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); }
我们回到setInitialValue()方法,程序执行倒数第二行createMap方法后,接着就返回了value,这个value也就是我们覆写initialValue()所返回的值(也就是副本)。createMap()方法只是把我们的副本保存在一个map中,同时为当前线程的threadLocals变量赋值。
以上便是ThreadLocal类的主要原理,知道了上面的知识,对于public void set(T value)方法就很好理解了!
细节注意:
1、在TheadLocal类中定义了privatestatic AtomicInteger nextHashCode = newAtomicInteger();,它是静态的,也就是在ThreadLocal对象之间是共享的数据。
2、ThreadLocal有一个静态的内部类ThreadLocalMap,它定义了privateEntry[]table;为什么是一个数组呢?一个线程可能有多个ThreadLocal对象,它们都保存在这个数组中了。怎么做到的呢?从ThreadLocal的源码我们可以看到,每次调用set方法的时候,都会先看看当前线程的threadLocals属性时候为空。如果不为空,就直接把我们的变量副本加到这个数组中。
转载地址:https://liangyihuai.blog.csdn.net/article/details/50755437 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!