Java面试必问ThreadLocal,所以想必大家对ThreadLocal并不陌生,从字面意思来看ThreadLocal很容易理解,但是想要真正理解并没有那么容易,今天我们就来扒下它的外衣……
1.首先我们来看看ThreadLocal如何使用,它能解决什么样的问题
ThreadLocal,线程本地变量,它可以为变量在每个线程中都创建一个副本,但它本身能够被多个线程共享使用,并且又能够达到线程安全的目的,且绝对线程安全。
来来来,For example:
ThreadLocal<String> strLocal1=new ThreadLocal<>();
public void setValue(){
strLocal1.set(Thread.currentThread().getName());
}
public String getStrLocal1(){
return strLocal1.get();
}
public void Run(){
setValue();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
Thread thread=new Thread(){
@Override
public void run() {
super.run();
setValue();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
}
};
thread.start();
System.out.println("<<<<"+Thread.currentThread().getName()+":"+getStrLocal1());
}
看看Log:
<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4
从这段代码的输出结果可以看出,在main
线程中和Thread-4
线程中,strLocal1
保存的副本值都不一样。最后一次在main
线程再次打印副本值是为了证明在main
线程中和Thread-4
线程中的副本值确实是不同的。
ThreadLocal存储的值,在每个线程中互不影响,是不是很容易就实现线程安全。
我们来看下ThreadLocal的源码,扒下它的遮羞布
先来看看ThreadLocal提供的几个方法:
/**
* 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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
get()
方法第一句是取得当前线程,然后通过getMap(t)方法获取到一个map,map的类型为ThreadLocalMap。然后接着下面获取到<key,value>键值对,如果获取成功,则返回value值。如果map为空,则调用setInitialValue方法返回value。
注意:ThreadLocalMap.Entry e = map.getEntry(this);
这里传进去的是this
,也就是ThreadLocal。
来看看getMap()
方法中干了什么:
/**
* 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;
}
在getMap中,是调用当期线程t,返回当前线程t中的一个成员变量threadLocals。
那么我们继续去Thread类中取看一下成员变量threadLocals是什么:
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
实际上就是一个ThreadLocalMap,这个类型是ThreadLocal类的一个内部类,我们继续取看ThreadLocalMap的实现:
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
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继承了WeakReference,并且使用ThreadLocal作为键值。
WeakReference是个什么东西呢?
它就是Java里面传说的:弱引用,弱引用用来描述非必需对象的,当JVM进行垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。在java中,用java.lang.ref.WeakReference类来表示,对于弱引用我们这里就不过多的讲解了,有时间我们也来扒下它神秘的外衣。
ThreadLocalMap中ThreadLocal做为key被保存在了WeakReference中,这就说明ThreadLocal在没有外部强引用时,发生GC时会被回收。
然后再继续看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;
}
很容易了解,就是如果map不为空,就设置键值对,为空,再创建Map。
先来看一下 T value = initialValue()
/**
* Returns the current thread's "initial value" for this
* thread-local variable. This method will be invoked the first
* time a thread accesses the variable with the {@link #get}
* method, unless the thread previously invoked the {@link #set}
* method, in which case the {@code initialValue} method will not
* be invoked for the thread. Normally, this method is invoked at
* most once per thread, but it may be invoked again in case of
* subsequent invocations of {@link #remove} followed by {@link #get}.
*
* <p>This implementation simply returns {@code null}; if the
* programmer desires thread-local variables to have an initial
* value other than {@code null}, {@code ThreadLocal} must be
* subclassed, and this method overridden. Typically, an
* anonymous inner class will be used.
*
* @return the initial value for this thread-local
*/
protected T initialValue() {
return null;
}
这里会返回null,会报空指针,所以我么在get()的时候,必须先set()
再来,看一下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);
}
再来看看这一行:
t.threadLocals = new ThreadLocalMap(this, firstValue);
* 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);
}
可能大部分朋友已经明白了ThreadLocal是如何为每个线程创建变量的副本的:
首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。
初始化时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。
再来看看ThreadLocal里面的set方法:
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
和setInitialValue()方法一样,如果map不为空,就设置键值对,为空,再创建Map,createMap(t, value)我们上面已经分析过了。
好了,该到总结的时候了:
1.通过ThreadLocal创建的副本,存储在每个线程自己的threadLocals中。
- threadLocals实际就是ThreadLocalMap,ThreadLocalMap把ThreadLocal做为key。
3.在进行get之前,必须先set,否则会报空指针异常。
4.如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。For example,修改一下上面的例子:
ThreadLocal<String> strLocal1 = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return Thread.currentThread().getName();
}
};
// public void setValue() {
// strLocal1.set(Thread.currentThread().getName());
// strLocal1.set("dddddd");
// }
public String getStrLocal1() {
return strLocal1.get();
}
public void Run() {
// setValue();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
Thread thread = new Thread() {
@Override
public void run() {
super.run();
// setValue();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
}
};
thread.start();
System.out.println("<<<<" + Thread.currentThread().getName() + ":" + getStrLocal1());
}
贴上Log:
<<<<main:main
<<<<main:main
<<<<Thread-4:Thread-4
没有报错哦,赶紧动手试试吧。