全文概要
本文主要介绍lang包下的ThreadLocal对象,主要内容如下:
- 简单分析下ThreadLocal源码
- 通过一个案例说明ThreadLocal的使用规则和场景
ThreadLocal源码分析
在ThreadLocal中最重要的就数get()/set()方法了,源码如下:
/**
* 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是ThreadLocal中一个静态内部类,他实现了Map
ThreadLocalMap map = getMap(t);
if (map != null) {
//Map中的key就是ThreadLocal对象
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
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;
}
//该方法定义成protected是为了使用者重写的
protected T initialValue() {
return null;
}
/**
* 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);
}
/**
* Get the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @return the map
*/
ThreadLocalMap getMap(Thread t) {
//在线程Thread中有一个成员变量记录线程绑定的数据
return t.threadLocals;
}
分析得知,ThreadLocal的原理如下:
- Thread中有一个成员变量属于ThreadLocalMap,而ThreadLocalMap是ThreadLocal的一个静态内部类,换句话说,每个线程都有一个独立的绑定该线程数据的变量;
- 当线程调用ThreadLocal的set方法时,会根据该线程对象得到ThreadLocalMap对象,然后将ThreadLocal对象作为key,将该线程需要传入的数据作为value,添加进ThreadLocalMap中,调用get方法也是一样。
ThreadLocal使用案例
下面以多个线程获取上下文打印日志为例,来说明ThreadLocal的使用。
上下文对象代码:
package com.tml.javaCore.thread.threadLocal;
/**
* <p>模拟上下文
* 通过上下文对象获取日志id
* @author Administrator
*
*/
public class Context {
private static final ThreadLocal<String> MY_THREAD_LOCAL = new ThreadLocal<String>(){
@Override
protected String initialValue() {
return "empty";
}
};
/*
* 设置当前线程的日志id
*/
public static void setLogId(String logId){
MY_THREAD_LOCAL.set(logId);
}
/*
* 获取当前线程的日志id
*/
public static String getLogId(){
return MY_THREAD_LOCAL.get();
}
}
日志工具类对象代码:
package com.tml.javaCore.thread.threadLocal;
/**
* <p>日志工具类
* @author Administrator
*
*/
public class LogUtil {
/*
* 模拟打印日志
*/
public static void printLog(){
System.out.println(Thread.currentThread().getName() + ":log id is:" + Context.getLogId());
}
}
测试案例代码:
package com.tml.javaCore.thread.threadLocal;
/**
* <p>ThreadLocal案例
* @author Administrator
*
*/
public class ThreadLocalDemo {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
//设置日志id
Context.setLogId("log_001");
//模拟web项目中层层调用
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印日志,这里没有将日志id传过去,在工具类中自行获取
LogUtil.printLog();
}
}, "thread_01").start();
new Thread(new Runnable() {
@Override
public void run() {
//设置日志id
Context.setLogId("log_002");
//模拟web项目中层层调用
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印日志,这里没有将日志id传过去,在工具类中自行获取
LogUtil.printLog();
}
}, "thread_02").start();
}
}
- 结果输出
thread_01:log id is:log_001thread_02:log id is:log_002
- 结果分析
- 上下文Context中,定义了一个ThreadLocal对象,里面有获取日志id和设置日志id的方法,setLogId()将对应的日志id添加到threadlocal对象中,getLogId()方法从threadlocal对象中获取日志id;
- 日志工具类LogUtil中定义了一个静态的printLog方法,该方法获取对应线程的日志id,打印日志;
- 测试主类中,创建了两个线程,线程1设置日志Id为log_001,线程2设置日志Id为log_002,接着模拟web项目中层层调用业务逻辑,最后调用工具类打印日志,调用的时候没有传入日志Id,而是通过日志工具类自行获取;
- 下面这个图可以很好说明两个线程与ThreadLocal对象之间的关系:
- 使用场景
可以总结一句话,ThreadLocal是提供线程内的局部变量,这种变量在线程的生命周期中起作用,减少同一个线程内多个函数或组件之间一些公共变量的传递(如上例中的日志Id)的复杂度。