遇到ThreadLocal怎么办?
前言
面试的时候经常会被问道ThreadLocal,哦吼,这是个啥?每次咱们只能回答ThreadLocal似乎可以将数据复制一份放到线程中以供线程使用,然后,嗯哼,就没了,可咋整,是不是很尴尬,这个时候面试官就会认为你还是知道那么点皮毛的,接着他就会问,那你知道是怎么保证每个线程都只取自己的数据吗?父线程能够拿到子线程的ThreadLocal数据吗?你平时都是使用在什么场景。。。这个时候可不是完全懵逼吗?呃。。。呃。。。不好意思我不会,那一首凉凉送给自己。
常问问题
- 你知道ThreadLocal吗?
- ThreadLocal你是怎么理解的?
- ThreadLocal使用在什么场景?
- ThreadLocal会存在内存泄露吗?为什么?
知识讲解
- 什么是ThreadLocal?
/**
* This class provides thread-local variables. These variables differ from
* their normal counterparts in that each thread that accesses one (via its
* {@code get} or {@code set} method) has its own, independently initialized
* copy of the variable. {@code ThreadLocal} instances are typically private
* static fields in classes that wish to associate state with a thread (e.g.,
* a user ID or Transaction ID).
*
* <p>For example, the class below generates unique identifiers local to each
* thread.
* A thread's id is assigned the first time it invokes {@code ThreadId.get()}
* and remains unchanged on subsequent calls.
* <pre>
* import java.util.concurrent.atomic.AtomicInteger;
*
* public class ThreadId {
* // Atomic integer containing the next thread ID to be assigned
* private static final AtomicInteger nextId = new AtomicInteger(0);
*
* // Thread local variable containing each thread's ID
* private static final ThreadLocal<Integer> threadId =
* new ThreadLocal<Integer>() {
* @Override protected Integer initialValue() {
* return nextId.getAndIncrement();
* }
* };
*
* // Returns the current thread's unique ID, assigning it if necessary
* public static int get() {
* return threadId.get();
* }
* }
* </pre>
* <p>Each thread holds an implicit reference to its copy of a thread-local
* variable as long as the thread is alive and the {@code ThreadLocal}
* instance is accessible; after a thread goes away, all of its copies of
* thread-local instances are subject to garbage collection (unless other
* references to these copies exist).
*
* @author Josh Bloch and Doug Lea
* @since 1.2
*/
先来看一波javadoc文档怎么说:
这个类提供线程局部变量。 这些变量与其正常的对应方式不同,因为访问一个的每个线程(通过其get或set方法)都有自己独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)。
从这里我们可以看到ThreadLocal是为线程提供一个数据副本,且这个数据是与该线程息息相关,这里也有说,比如用户的id,延伸到用户session和事务id等
- ThreadLocal的set、get方法
/**
* 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.
* 设置此线程局部变量的当前线程副本到指定值。大多数子类将不需要
*重写此方法,仅依赖{@link#initialValue}(初始化)
*方法设置线程局部变量的值
* @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);
}
/**
* 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.
*返回当前线程副本中的值
*线程局部变量。如果变量没有
*当前线程,它首先初始化为返回的值
*通过调用{@link#initialValue}方法
* @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();
}
- ThreadLocal的内存泄漏
ThreadLocalMap中的Entry的key使用的是ThreadLocal对象的弱引用,在没有其他地方对ThreadLoca依赖,ThreadLocalMap中的ThreadLocal对象就会被回收掉,但是对应的不会被回收,这个时候Map中就可能存在key为null但是value不为null的项,这需要实际的时候使用完毕及时调用remove方法避免内存泄漏。 - 使用举例
/**
* @author 13738
*/
public class ForThreadLocal {
public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static ThreadLocal<String> threadLocal2 = new ThreadLocal<>();
public static void main(String[] args) {
testThreadLocal();
}
public static void testThreadLocal() {
ExecutorService executors = Executors.newFixedThreadPool(5);
for (int i = 1; i < 5; i++) {
String j = i + "chai";
String x = i + "sanRen";
executors.submit(() -> {
threadLocal.set(j);
threadLocal2.set(x);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + threadLocal.get());
System.out.println(Thread.currentThread().getName() + threadLocal2.get());
});
}
threadLocal.remove();
threadLocal2.remove();
executors.shutdown();
}
}
上述代码说明:
1.ThreadLocal中可以存放多个线程的数据,每个线程的数据key为线程id
2.一个线程可以往多个ThreadLocal对象存储数据,但同一个ThreadLocal对象存储多次会被覆盖
二、面试回答
面试回答时我们主要从以下几个方面切入答案:
- 说说ThreadLocal是什么?
首先,ThreadLocal一般称为线程本地变量或者线程本地存储,其内部维护了一个ThreadLocalMap的静态内部类,通过set、get、remove等方法,来操作每个线程私有的ThreadLocalMap变量,以实现线程间数据的隔离。 - 讲讲ThreadLocal的应用场景有哪些?
主要应用场景:
1.每个线程都需要自己对应的实例
2.多个方法共享实例,但又不被多个线程使用
举例:用户session传递,数据库连接,spring事务,日志等 - 谈谈使用ThreadLocal的好处以及注意点?
ThreadLocal的使用既可以保证线程安全,同时能够降低代码耦合度,实现更加优雅的代码,但是要注意ThreadLocal存在内存泄漏的问题,解决是每次使用完以后需要remove方法清除 - ThreadLocal的子类InheritableThreadLocal
由于ThreadLocal线程私有的特性,父线程与子线程之间数据也是无法共享,这是可以通过InheritableThreadLocal来实现。