ThreadLocal面试题

ThreadLocal是什么?
ThreadLocal是用来维护线程中的变量不被其他线程干扰而出现的一个结构,内部包含一个ThreadLocalMap类,该类为Thread类的一个局部变量,该Map存储的key为ThreadLocal对象自身,value为我们要存储的对象,这样一来,在不同线程中,持有的其实都是当前线程的变量副本,与其他线程完全隔离,以此来保证线程执行过程中不受其他线程的影响。
1
下面我们通过代码看下它如何保证线程安全:

ThreadLocal结构:

主要是四个方法:
1. void set(Object value)
设置当前线程的线程局部变量的值。
2. public Object get()
该方法返回当前线程所对应的线程局部变量。
3. public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
4. protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal。API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。

我们看下set方法的实现:

public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
可以看到,先通过Thread.currentThread()方法获取到了当前线程,然后如果取不到map对象,就会创建,下面看下create方法

void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
很简单的方法,就是新建一个ThreadLocal的Map,这个map以ThreadLocal自身为key,以我们要设值的对象为value,创建出来map之后,将对象赋值到线程的局部变量去。
看到这里,就知道ThreadLocal主要目的就是将变量设值到当前的线程上,以此来保证线程安全。
那么下面看下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();
}
可以看出来,get方法就是拿的当前线程的局部变量threadLocals,然后从中取出map中存储的对象,这样每个线程中获取的一定是自己线程中存储的对象了。
由上述分析可知,使用ThreadLocal是可以保证线程安全的。
ThreadLocal实际上是为解决多线程程序的并发问题提供了一种新的思路(区别与sychronized关键字)。
ThreadLocal这个类提供线程本地的变量。这些变量与一般正常的变量不同,它们在每个线程中都是独立的。ThreadLocal实例最典型的运用就是在类的私有静态变量中定义,并与线程关联。
我们看下具体的使用场景吧:

我们知道SimpleDateFormat是非线程安全的,并发场景下会出现格式化时间错误的问题,那么在这里我们就可以使用ThreadLocal来解决此类问题。
private static ThreadLocal<SimpleDateFormat> sdt = new ThreadLocal<SimpleDateFormat>();

public static final SimpleDateFormat getSdt() {
if (null == sdt.get()) {
sdt.set(new SimpleDateFormat("MM月dd日HH:mm"));
}
return sdt.get();
}

看另外一个场景,一个请求过来,会带有一堆的c参数(可以理解为客户端的一些标志),我们的应用处理过程中,大部分地方又不需要关心该参数,可能在某个请求他人接口的时候需要了,如果我们把所有代码都带上这个c参数,那么未免代码看着太过丑陋,这种情况下,我们可以构建一个filter,在请求过来的时候,在filter中将c参数放置到ThreadLocal中,在整个调用链中如果需要使用,直接从ThreadLocal中获取即可。

另外一个例子是动态数据源的使用,我们可以使用ThreadLocal来保证当次线程调用中只使用一种数据源。这部分内容下个文章我会单独列出来讲解一下。
最后我们在提一点,就是使用ThreadLocal是否会造成内存泄漏,这一点答出来相信应该会给自己很多加分吧~
这个是ThreadLocal自身的注释:

每一个线程对资源副本都有一个weekRefrence:只要线程还在运行,使用ThreadLocal就是可以获取的。当一个线程运行结束销毁时,所有的资源副本都是可以被垃圾回收的(ThreadLocalMap对象保存在Thread对象中,当某个线程终止后,存储在其中的线程隔离的变量,也将作为Thread实例的垃圾被回收掉,所以完全不用担心内存泄漏的问题),这段注释表明,ThreadLocal的使用是不会造成内训泄露的。
---------------------
作者:曹怡帅
来源:CSDN
原文:https://blog.csdn.net/caoyishuai100/article/details/68946037
版权声明:本文为博主原创文章,转载请附上博文链接!

posted @ 2019-06-03 21:19  lllunaticer  阅读(439)  评论(0编辑  收藏  举报