此类提供线程局部变量。这些变量与普通变量不同,每个访问一个线程(通过其get
或set
方法)的线程 都有其自己的,独立初始化的变量副本。 ThreadLocal
实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
以射击游戏举例,游戏开始时,每个人能够领到一把枪,枪把上有三个数字:子弹数、杀敌数、自己的命数,为其设置的初始值分别为100、0、10.设战场上的每个人都是一个线程,那么这三个初始值写在哪里呢?
如果每个线程都写死这三个值,万一将初始子弹数统一改成 1000发呢?
如果共享,那么线程之间的并发修改会导致数据不准确.
能不能构造这样一个对象,将这个对象设置为共享变量,统一设置初始值,但是每个线程对这个值的修改都是互相独立的.这个对象就是ThreadLocal
一、类定义
public class ThreadLocal<T> {}
…用来限制Class中的参数类型,确保Class中参数的一致性
二、实例变量和相关方法
//用于ThreadLocalMap
private final int threadLocalHashCode = nextHashCode();//下一个hash code,从0开始
private static AtomicInteger nextHashCode = new AtomicInteger();//hash增量
private static final int HASH_INCREMENT = 0x61c88647;//在获取下一个hash code
private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);
}
三、内部类
内部类:ThreadLocalMap
ThreadLocalMap
负责存储ThreadLocal
及其变量,即ThreadLocal
对象本身作为键,ThreadLocal
存储的变量作为值。每个Thread
对象在声明一个ThreadLocal
后会持有一个ThreadLocalMap
的引用,来实现ThreadLocal
的功能。
ThreadLocalMap
持有一个内部类Entry
,类似于HashMap.Node
类,负责保存键值对。
static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}
}
Entry
继承了WeakReference
类,使Entry
的键为弱引用。
看到这里很多人或许有这样一个疑问:为什么Entry要继承WeakReference?
既然将ThreadLocal
声明为弱引用,那么自然会联想到和GC有关。
如果不声明为弱引用,以最上面Test类的代码为例,当我们将上述ThreadLocal
类型的静态变量tl
设置为null
时,Thread
对象成员变量threadLocals
依然保留有一个ThreadLocalMap
,该Map中持有保存该ThreadLocal
的Entry
,在这个线程运行期间无法GC,从而引发内存泄漏。所以,Entry
的键要声明为弱引用。
四、主要方法
1.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();
}
首先是获取当前运行线程对象,然后根据该线程对象找到对应的ThreadLocalMap
。
- 如果找到了该线程对应的
ThreadLocalMap
,则通过当前ThreadLocal
对象作为键查找Map
中对应的Entry
(键值对)对象 - 如果查找结果不为
null
,则返回Entry
对象的value
。否则调用setInitialValue
方法将当前ThreadLocal
对象(this)和变量作为键值对存入ThreadLocalMap
并返回变量。
2.initialValue()
为变量设置初始值,该方法的默认实现是:
protected T initialValue() {return null;
}
如果想要为该变量设置一个初始值,只需重写该方法即可,例如:
@Override
protected String initialValue() {return "hello world";
}
3.set(T value)
与get
方法类似,set
方法首先会获取当前运行的Thread
对象并通过该对象获取对应的ThreadLocalMap
,如果map为空,则为当前Thread
对象新建一个ThreadLocalMap
,否则直接将value
放入map
中。
public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
4、remove()
同样,获取当前Thread
对应的ThreadLocalMap
,然后调用ThreadLocalMap
的remove
方法移除ThreadLocal
对象,无需通过弱引用机制对该ThreadLocal
对象进行GC。
public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
五、总结
ThreadLocal是如何做到为每一个线程维护变量的副本的呢?
在ThreadLocal
类中设置了一个Map,存储每一个线程的变量的副本。
ThreadLocal
使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
Threadlocal
底层是通过threadlocalMap
进行存储键值 每个ThreadLocal
类创建一个Map,然后用线程的ID作为Map的key,实例对象作为Map的value,这样就能达到各个线程的值隔离的效果。
ThreadLocal
的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。