ThreadLocal,是Java中一种特殊的变量。每一个线程都有一个独立的ThreadLocal变量。它也算是为避免花费相对较大的代价去创建对象来获取线程安全的好方法。我们知道,SimpleDateFormat不是线程安全的,我们可以用ThreadLocal让SimpleDateFormat变成线程安全的,这样也在一定程度上提高了效率。如下例子:
private static final ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
@Override
protected SimpleDateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
但是有一点要记住,ThreadLocal虽然提供了一种解决多线程环境下成员变量的问题,但是它并不是解决多线程共享变量的问题。ThreadLocal提供了线程局部 (thread-local) 变量。这些变量不同于它们的普通对应物,因为访问某个变量(通过其get / set方法)的每个线程都有自己的局部变量,它独立于变量的初始化副本。ThreadLocal实例通常是类中的 private static 字段,它们希望将状态与某一个线程(如:用户 ID ,事务 ID)相关联。
先了解一下ThreadLocal类提供的几个方法:
//返回此线程局部变量的当前线程副本中的值。
public T get() { }
//将此线程局部变量的当前线程副本中的值设置为指定值。
public void set(T value) { }
//移除此线程局部变量当前线程的值。
public void remove() { }
//返回此线程局部变量的当前线程的“初始值”。
protected T initialValue() { }
除了上面四个方法,ThreadLocal内部还有一个静态内部类ThreadLocalMap,该内部类才是实现线程隔离机制的关键,get()、set()、remove()都是基于该内部类操作。ThreadLocalMap提供了一种用键值对方式存储每一个线程的变量副本的方法,key为当前ThreadLocal对象,value则是对应线程的变量副本。

注意:1)ThreadLocal实例本身是不存储值的,它只是提供了一个在当前线程中找到的副本key(如上图)。
2)ThreadLocal包含在Thread中,尔不是Thread包含在ThreadLocal中。
ThreadLocal的源码我们先从ThreadLocalMap入手
static class ThreadLocalMap {
/**
* 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) 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;
}
}
.........
}
ThreadLocalMap内部是通过Entry实现的一个Key-Value的存储的,并且key就是ThreadLoal,value就是值。而且Entry继承了WeakReference(弱引用)。
注:
弱引用是对一个对象(称做Referent)的引用的持有者。使用弱引用,可维持对referent对引用,尔不会阻止它被垃圾回收。当垃圾收集器跟踪队对时候,若对一个对象对引用只有弱引用,那么这个referent就会成为垃圾收集对候选对象,如同没有其他对引用一样,而且所有剩余对弱引用都会被清除。此外除了弱引用还有强引用(StrongReference),软引用(SoftReference),虚引用(WeakReference)等。
经常有人会被问SoftReference与WeakReference对区别,它们二者对区别就在于垃圾回收对时机不同,系统程序空间足够的时候SoftReference不会被垃圾回收,程序也可以使用该对象,只有空间不足的时候才会被回收;但是WeakReference不同,不管空间内存是否足够,都会回收该对象占用的内存空间。
1、先看get()
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从当前线程的ThreadLocalMap获取相对应的Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
// 获取目标值
T result = (T)e.value;
return result;
}
}
//若没有获取到ThreadLocalMap则返回初始值,默认为null
return setInitialValue();
}
初始化方法:
/**
* 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;
}
getMap(Thread t)可以获取当前线程所有的ThreadLocalMap:
/**
* 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;
}
Thread.java
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
2、set(T value):
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
获取当前线程所对应的ThreadLocalMap,如果不为空,则调用ThreadLocalMap的set()方法,key就是当前ThreadLocal,如果不存在,则调用createMap()方法新建一个,如下:
/**
* 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
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
看到这里,我们可以得知ThreadLocal类是如何为每个线程创建一个变量的副本的。
首先,在每个Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,该threadLocals就是用来存储实际的变量副本的,key为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
其次,在初始的时候,threadLocals为null,当通过ThreadLocal变量调用get()或者set(T value)的时候(creatMap(t,value)),就会对Thread类中对threadlocals进行初始化,并且以当前ThreadLocal为key, 以ThreadLocal要保存的副本变量为value,存到threadLocals。
最后,在当前Thread中,如果要使用副本变量,就可以通过get()在threadLocals中进行查找了。
3、initialValue()
protected T initialValue() {
return null;
}
protected级别且返回为null,这明显是要子类实现它的,所以在使用ThreadLocal的时候一般都应该覆盖该方法(如本文开头对SimpleDateFormat)。该方法不能显示调用,只有在第一次调用get()或者set()方法时才会被执行,并且仅执行1次。
4、remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
该方法的目的是减少内存的占用。一般情况,不需要显示调用该方法,因为一个线程结束后,它所对应的局部变量就会被垃圾回收,但是用ThreadLocal最好是每次使用完就调用remove方法,将其删掉,避免先get后set的情况导致业务的错误(至于原因大家感兴趣可以看下ThreadLocal不调用remove方法会导致业务逻辑错误)。
另外,InheritableThreadLocal 类,是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。附:inheritableThreadLocal 原理
本文深入探讨Java中的ThreadLocal,一种确保线程安全的特殊变量,通过实例讲解如何使用ThreadLocal使SimpleDateFormat线程安全,剖析ThreadLocal的工作原理,包括其内部类ThreadLocalMap的机制,以及get(), set(), remove(), initialValue()等关键方法的实现。
10万+

被折叠的 条评论
为什么被折叠?



