TheadLocal简介
ThreadLocal设计的目的就是为了能够在当前线程中有属于自己的变量,并不是为了解决并发或者共享变量的问题!(说白了就是每个线程独有的变量,自然不存在并发问题)
谁再说ThreadLocal解决了并发问题,呼死他,根本没有半毛钱的关系!!!
ThreadLocal实现原理
想要更好地去理解ThreadLocal,那就得翻翻它是怎么实现的了~~~
声明:本文使用的是JDK 1.8
首先,我们来看一下ThreadLocal的set()方法,因为我们一般使用都是new完对象,就往里边set了
public void set(T value) {
// 得到当前线程对象
Thread t = Thread.currentThread();
// 这里获取ThreadLocalMap
ThreadLocalMap map = getMap(t);
// 如果map存在,则将当前的threadlocal对象作为key,要存储的对象作为value存到map里面去
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
上面有个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是ThreadLocal的一个静态内部类,用Entry类来进行存储,我们的值都是存储到这个Map上的,key是当前ThreadLocal对象!
如果该Map不存在,则初始化一个:
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
如果该Map存在,则从Thread中获取!
/**
* 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维护了ThreadLocalMap变量
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null
从上面又可以看出,ThreadLocalMap是在ThreadLocal中使用静态内部类来编写的,但对象的引用是在Thread中!
于是我们可以总结出:Thread为每个线程维护了ThreadLocalMap这么一个Map成员变量,而ThreadLocalMap的key是LocalThread对象本身,value则是要存储的对象。
有了上面的基础,我们看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();
}
ThreadLocal原理总结
- 每个Thread维护着一个ThreadLocalMap的引用;
- ThreadLocalMap是ThreadLocal的内部类,用Entry来进行存储;
- 调用ThreadLocal的set()方法时,实际上就是往ThreadLocalMap设置值,key是ThreadLocal对象,值是传递进来的对象;
- 调用ThreadLocal的get()方法时,实际上就是往ThreadLocalMap获取值,key是ThreadLocal对象;
- ThreadLocal本身并不存储值,它只是作为一个key来让线程从ThreadLocalMap获取value。
ThreadLocal的使用场景
1. 避免重复创建对象
对于一些工具类(SimpleDateFormat,Random),在一次请求中可能被多次用到(需要多次创建对象),每次用的时候就:
SimpleDateFormat formator = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
formator.format(date);
使用ThreadLocal后:
public class DateUtil {
private static ThreadLocal<DateFormat> yyyyMMdd_HHmmss = new ThreadLocal() {
@Override
protected DateFormat initialValue() {
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
}
};
public static String format_yyyyMMdd_HHmmss(Date date) {
if (date == null) {
return null;
}
return yyyyMMdd_HHmmss.get().format(date);
}
public static Date parse_yyyyMMdd_HHmmss(String date) {
if (date == null) {
return null;
}
try {
return yyyyMMdd_HHmmss.get().parse(date);
} catch (ParseException e) {
throw new RuntimeException("时间解析异常:" + date, e);
}
}
}
非常的好用~~~ (感觉也不咋实用)
2. 避免大量参数传递
toC的应用开发中,对于用户的请求我们服务端一般都会进行风控检验(请求风控入参会包括用户信息、设备信息等),来判断用户是不是危险用户,从而进行限制请求。而用户的设备信息在请求打到服务端时就已经解析拿到。处理链路如下:
设备信息我们几乎不care,但是方法handle3中调用风控会用到,而不得不一层层透传,非常难受...
使用ThreadLocal后,就变成了:
非常的好用~~~
// 实际开发中,我们会定义DeviceInfo注解,自动解析用户信息、设备信息放到ThreadLocal中
public class DeviceInfoHolder {
private static ThreadLocal<BaseDeviceInfo> DEVICE_HOLDER = new ThreadLocal<>();
public static void setDeviceInfo(BaseDeviceInfo deviceInfo) {
DEVICE_HOLDER.set(deviceInfo);
}
public static BaseDeviceInfo getDeviceInfo() {
BaseDeviceInfo baseDeviceInfo = DEVICE_HOLDER.get();
if (baseDeviceInfo == null) {
throw new IllegalStateException("lack device info");
}
return baseDeviceInfo;
}
public static void reset() {
DEVICE_HOLDER.remove();
}
}
@Aspect
@Component
public class DeviceInfoAspect {
@Around("@annotation(com.xxx.xxx.annotation.DeviceInfo)")
public Object deviceInfo(ProceedingJoinPoint joinPoint) throws Throwable {
AbstractRequest requestBody = null;
Object[] args = joinPoint.getArgs();
for (Object arg : args) {
if (arg instanceof AbstractRequest) {
requestBody = (AbstractRequest) arg;
break;
}
}
// ... 从http请求中解析出用户信息、设备信息
BaseDeviceInfo deviceInfo = DeviceInfoAssembler.MAPPER.toDeviceInfo(requestBody);
DeviceInfoHolder.setDeviceInfo(deviceInfo);
try {
return joinPoint.proceed();
} finally {
DeviceInfoHolder.reset();
}
}
}
ThreadLocal的内存泄漏
我们来看一下ThreadLocal的对象关系引用图:
ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
想要避免内存泄露就要手动remove()掉!
自己画的TheadLocal对象关系引用图:
感谢大佬们: Java并发-ThreadLocal 、ThreadLocal就是这么简单