1. ThreadLocal是什么?有什么作用?
ThreadLocal一般认为线程本地变量,其主要为线程提供一份共享变量的本地副本,因此,每个线程可以单独访问本地副本,而不受其他线程的影响。
2. ThreadLocal是如何保证线程安全的?
ThreadLocal主要从两方面保证了线程安全,其中一方面,通过为每个线程提供独立的ThreadLocalMap<key, value>实例对象,该实例对象以threadLocal实例作为键值,共享变量的副本作为value值,因此,不同线程持有的ThreadLocalMap实例对象时不同的;另一方面,ThreadLocalMap可以同时保存不同ThreadLocal实例对象保存的本地副本,因此同一个线程可以存储不同ThreadLocal实例存储的本地副本。
3. ThreadLocal的工作原理?
在了解ThreadLocal工作原理之前,首先了解下ThreaLocal类提供的几个公共方法:
(1) public
T get() { }
(2) public
void
set(T value) { }
(3)public
void
remove() { }
(4)protected
T initialValue() { }
其中,get()方法用户获取当前ThreadLocal实例对象保存的本地副本;set()方法主要为当前ThreadLocal实例对象设置新的副本对象;remove()方法主要移除当前ThreadLocal实例对象保存的本地副本;initialValue()方法主要为当前ThreadLocal实例对象初始化,一般在使用需要重写,是一个延迟加载方法。
首先,看下ThreadLocal是怎么样为线程创建独立的本地副本,查看ThreadLocal类的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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
在get()方法中,首先获得当前线程实例,其次,通过getMap()方法获取当前线程的ThreadLocalMap实例,由于ThreadLocalMap实例通过ThreadLocal实例作为键值存储本地副本,所以通过map.getEntry(this)获取存储本地副本的键值对。
接下来,看下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持有的ThreadLocalMap对象。
接着看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;
}
在setInitialValue()方法中,首先需要初始化一个value(本地副本实例对象),其次通过当前线程对象获取到ThreadLocalMap实例,若当前map对象不为空,则将value值添加至该map对象中,其中this就是当前ThreadLocal对象实例本身;若map对象为空,则创建一个ThreadLocalMap对象。
我们接着往下看createMap(t, value)做了哪些事情,
/**
* 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);
}
/**
* 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);
}
/**
* The initial capacity -- MUST be a power of two.
*/
private static final int INITIAL_CAPACITY = 16;
createMap方法主要为当前线程t创建一个ThreadLocalMap实例,该Map实例用于存储当前线程的ThreadLocal对象保存的所有本地副本实例。其中ThreadLocalMap对象的初始化容量为16,类似于HashMap的初始化过程。上述为get方法所涉及的相关处理过程。
接下来看看set(T value)方法的源码实现
/**
* 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);
}
从set(T value)源码可以看出和setInitialValue()的流程很相似,就不再重复叙述了。
接着看remove()方法的源码实现,
/**
* Removes the current thread's value for this thread-local
* variable. If this thread-local variable is subsequently
* {@linkplain #get read} by the current thread, its value will be
* reinitialized by invoking its {@link #initialValue} method,
* unless its value is {@linkplain #set set} by the current thread
* in the interim. This may result in multiple invocations of the
* {@code initialValue} method in the current thread.
*
* @since 1.5
*/
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
从remove的源码实现中,可以看出该方法主要用于移除以当前ThreadLocal实例生成的Entry键值对。
在我们看完ThreadLocal类中常用方法的源码实现后,是否仍存在一个疑问?ThreadLocal类是如何为共享变量创建一个当前线程的一个本地副本对象呢?然而在揭开这个疑问之前,我们需要了解下,ThreadLocal类提供一个供子类实现的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;
}
从源码中,可以看出ThreadLocal类并未对该方法实现做相应的工作,只是简单的返回一个null值,因此,当我们在实际使用的过程中需要我们自己去重新实现该方法,用于创建一个本地副本实例。
4. 测试验证
在完成ThreadLocal类的相关源码学习后,现进行实例测试验证, 测试代码如下:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>(){
@Override
public Demo initialValue(){
Demo demo = new Demo();
System.out.println("intial value, thread-name: "+ Thread.currentThread()+" demo: "+demo+", demoName: "+demo.getName());
return demo;
}
};
Demo demo = new Demo();
demo.setName("zhansan");
demo.setRole("admin");
for(int i=0; i<2; i++){
Thread thread = new Thread(new WorkRunnable(threadLocal, demo, "testName-"+i, "testRole-"+i));
thread.setName("thread-"+i);
thread.start();
}
}
private static class WorkRunnable implements Runnable{
private ThreadLocal<Demo> threadLocal;
private String name;
private String role;
public WorkRunnable(ThreadLocal<Demo> threadLocal, Demo demo, String name, String role) {
// TODO Auto-generated constructor stub
this.threadLocal = threadLocal;
threadLocal.set(demo);
this.name =name;
this.role = role;
System.out.println("thread-name: "+Thread.currentThread()+ "; demo: "+demo);
}
@Override
public void run(){
Demo demo = threadLocal.get();
//demo.setName(name);
//demo.setRole(role);
System.out.println("thread name: "+Thread.currentThread()+"; demo: "+ demo+"; demo.name: "+demo.getName()+"; demo.role: "+demo.getRole());
}
}
}
测试结果如下:
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
intial value, thread-name: Thread[thread-0,5,main] demo: Demo@26830c2b, demoName: null
intial value, thread-name: Thread[thread-1,5,main] demo: Demo@15449d43, demoName: null
thread name: Thread[thread-1,5,main]; demo: Demo@15449d43; demo.name: null; demo.role: null
thread name: Thread[thread-0,5,main]; demo: Demo@26830c2b; demo.name: null; demo.role: null
从该测试结果看出,ThreadLocal类为当前线程创建的本地副本是通过initialValue()创建完成的,由于在创建本地副本对象时为写入相关属性,因此输出的属性值均为null.
现我们通过添加这段代码后,并查看测试结果:
demo.setName(name);
demo.setRole(role);
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
intial value, thread-name: Thread[thread-0,5,main] demo: Demo@15449d43, demoName: null
intial value, thread-name: Thread[thread-1,5,main] demo: Demo@6304a40b, demoName: null
thread name: Thread[thread-1,5,main]; demo: Demo@6304a40b; demo.name: testName-1; demo.role: testRole-1
thread name: Thread[thread-0,5,main]; demo: Demo@15449d43; demo.name: testName-0; demo.role: testRole-0
通过测试结果,看出ThreadLocal保存的当前线程副本中属性均已被赋值。
假设我们在ThreadLocal子类中未实现initialValue()方法,那将会发生什么情况呢?相关测试代码及测试结果如下:
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadLocal<Demo> threadLocal = new ThreadLocal<Demo>();
// {
// @Override
// public Demo initialValue(){
// Demo demo = new Demo();
// System.out.println("intial value, thread-name: "+ Thread.currentThread()+" demo: "+demo+", demoName: "+demo.getName());
// return demo;
// }
//
// };
Demo demo = new Demo();
demo.setName("zhansan");
demo.setRole("admin");
for(int i=0; i<2; i++){
Thread thread = new Thread(new WorkRunnable(threadLocal, demo, "testName-"+i, "testRole-"+i));
thread.setName("thread-"+i);
//thread.run();
thread.start();
}
}
private static class WorkRunnable implements Runnable{
private ThreadLocal<Demo> threadLocal;
private String name;
private String role;
public WorkRunnable(ThreadLocal<Demo> threadLocal, Demo demo, String name, String role) {
// TODO Auto-generated constructor stub
this.threadLocal = threadLocal;
threadLocal.set(demo);
this.name =name;
this.role = role;
System.out.println("thread-name: "+Thread.currentThread()+ "; demo: "+demo);
}
@Override
public void run(){
Demo demo = threadLocal.get();
demo.setName(name);
demo.setRole(role);
System.out.println("thread name: "+Thread.currentThread()+"; demo: "+ demo+"; demo.name: "+demo.getName()+"; demo.role: "+demo.getRole());
}
}
}
测试结果如下:
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
thread-name: Thread[main,5,main]; demo: Demo@2a139a55
Exception in thread "thread-0" Exception in thread "thread-1" java.lang.NullPointerException
at Test$WorkRunnable.run(Test.java:43)
at java.lang.Thread.run(Thread.java:745)
java.lang.NullPointerException
at Test$WorkRunnable.run(Test.java:43)
at java.lang.Thread.run(Thread.java:745)
通过测试结果发现,若ThreadLocal子类中为实现initialVlaue()方法,则出现.NullPointerException,说明ThreadLocal类为该当前线程未创建本地副本实例对象,因此我们在实际使用过程中,需要根据自己的实际使用情况,实现ThreadLocal类中提供的initialValue()方法创建本地副本对象。
如上所述,若存在遗漏或错误的地方,望大家积极评论指点。