看javaguide的过程中有些疑惑,此文很棒,很多参考于此https://www.cnblogs.com/xzwblog/p/7227509.html。
通常情况下,我们创建的变量可以给各个线程访问和修改,如果想要每个线程拥有自己本地专属变量,可以使用ThreadLocal类来解决。ThreadLocal相当于一个容器,就像ArrayList中能放各种类型的变量从而形成一个集合一样,ThreadLocal就像
一个盒子,将某种类型的变量放进去就成为了可被线程创建副本的一种数据类型,对象声明为
ThradLocal<数据类型> 对象名 = new ThreadLocal<>();
就像 ArrayList<Integer> list = new ArrayList<>();
若创建了一个ThreadLocal变量,则每个访问此变量的值都会在本地创建一个此变量的副本,这些线程可以使用get()和set()方法来获取变量副本的值或者修改副本值,从而避免线程安全问题。
那ThreadLocal是如何实现此功能的呢?通过查看原码,Thread类中有一行,定义ThreadLocalMap类型的变量threadLocals(初始为null 空)
/* ThreadLocal values pertaining to this thread. This map is maintained by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
对于这里出现的ThreadLocalMap类,我们可以将之理解为ThreadLocal类实现的定制化的HashMap,
其中key值是ThreadLocal类的变量,value是此变量的具体值。(而非网上有人说的key是线程名thread)
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
也就是说对于任一线程,当其访问ThreadLocal变量时候,会在线程内部创建一个ThreadLocalMap,此表记录了该线程访问了哪些ThreadLocal类的变量,一旦访问ThreadLocal
类变量就会在线程内部创建本地变量。副本在线程内部使用set()方法修改某个ThreadLocal类变量的值的时候,先根据此变量找到Map中对应的key,然后再修改此key对应的value,修改过程只发生在线程内部,修改的是副本值,与线程外定义的
ThreadLocal类变量无关。下面看ThreadLocal的set()和get的原码即可清晰理解
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}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();
}
其中的getMap方法:
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
给当前Thread类对象初始化ThreadlocalMap属性:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
因为这些方法都是ThreadLocal类的方法,上述this即指当前ThreadLocal类的对象,并用初始值为value创建ThreadLocalMap。
看到这里可以很清晰理解为什么该变量取名为"threadLocals",即指线程中的ThreadLocalMap中存着很多个ThreadLocal类变量,用"s"表示很多个。
到这里,我们就可以理解ThreadLocal究竟是如何工作的了
1.Thread类中有一个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,他的key是ThreadLocal实例对象。
2.当为ThreadLocal类的对象set值时,首先获得当前线程的ThreadLocalMap类属性,然后以ThreadLocal类的对象为key,设定value。get值时则类似。
3.ThreadLocal变量的活动范围为某线程,是该线程“专有的,独自霸占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
4.由ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是共享的
hreadLocal的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用。作用:提供一个线程内公共变量(比如本次请求的用户信息),减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
如何实现一个线程多个ThreadLocal对象,每一个ThreadLocal对象是如何区分的呢?
查看源码,可以看到:
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61c88647;
private static int nextHashCode() {
return nextHashCode.getAndAdd(HASH_INCREMENT);
}
对于每一个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashCode不可变属性,对于基本数据类型,可以认为它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象。
但是如何保证两个同时实例化的ThreadLocal对象有不同的threadLocalHashCode属性:在ThreadLocal类中,还包含了一个static修饰的AtomicInteger([əˈtɒmɪk]提供原子操作的Integer类)成员变量(即类变量)和一个static final修饰的常量(作为两个相邻nextHashCode的差值)。由于nextHashCode是类变量,所以每一次调用ThreadLocal类都可以保证nextHashCode被更新到新的值,并且下一次调用ThreadLocal类这个被更新的值仍然可用,同时AtomicInteger保证了nextHashCode自增的原子性。
为什么不直接用线程id来作为ThreadLocalMap的key?
这一点很容易理解,因为直接用线程id来作为ThreadLocalMap的key,无法区分放入ThreadLocalMap中的多个value。比如我们放入了两个字符串,你如何知道我要取出来的是哪一个字符串呢?
而使用ThreadLocal作为key就不一样了,由于每一个ThreadLocal对象都可以由threadLocalHashCode属性唯一区分或者说每一个ThreadLocal对象都可以由这个对象的名字唯一区分(下面的例子),所以可以用不同的ThreadLocal作为key,区分不同的value,方便存取。
public class Son implements Cloneable{
public static void main(String[] args){
Thread t = new Thread(new Runnable(){
public void run(){
ThreadLocal<Son> threadLocal1 = new ThreadLocal<>();
threadLocal1.set(new Son());
System.out.println(threadLocal1.get());
ThreadLocal<Son> threadLocal2 = new ThreadLocal<>();
threadLocal2.set(new Son());
System.out.println(threadLocal2.get()); }});
t.start();
}
}