ThreadLocal使用场景
并发编程中存在两种情况:
1、所有线程共享一个变量,且任意一线程对该变量的执行结果的改变会影响其他线程,此种情况下需对该变量加锁,防止脏数据的产生。(例如:多窗口购买火车票,每个窗口的火车票剩余数量的改变会直接影响其他窗口)
2、所有线程用一个变量名,但各个线程对该变量结果的改变不影响其他线程,使改变的结果保持在当前线程内。例如我整个项目每个方法都会用到userId(用户ID),这样我每一个方法的入参都需要附带一个userId,由于每个用户对应不同的线程,且每个用户的userId不同,彼此隔离没有任何关系,这种情况适用ThreadLocal,可为每一个线程提供一个线程级别的全局变量副本。使用ThreadLocal后,不再需要方法体提供userId入参,而是在全局想哪里获取就哪里获取,增加了代码的灵活性,减少了冗余。
ThreadLocal理解
1、ThreadLocal不同于共享变量,其在多个线程中对应多个副本,且每个副本之间没有关联。
2、ThreadLocal不适用于线程同步,其出现的目的只是为了各个线程维护自己的变量。
ThreadLocal源码分析
jdk版本:1.8
首先在打开ThreadLocal类,首部有这样一段注释来描述这个类,大概意思是:这个类提供线程级别的本地变量,这些变量通过访问get和set方法拥有自己独立的初始化变量的线程级别的副本。典型的使用方法是在类中定义私有静态(private static)成员变量。每一个线程都拥有其对本地线程副本的隐式引用,只要线程活着,该本地变量就可用;当这个线程生命结束时,它的所有副本将被垃圾回收(除非存在其他对这些副本的引用)。
/**
* 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).
*
* 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
*/
public class ThreadLocal<T> {***}
首先看set方法
由于该本地变量要和线程绑定,常规做法是将他们捆绑放入Map中。
/**
* 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
ThreadLocalMap map = getMap(t);
// 若map不存在,则在map中放入当前ThreadLocal和value;否则createMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap方法通过当前线程获取ThreadLocalMap(ThreadLocalMap可以理解为一个Map,但实际上不是一个Map)
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
ThreadLocalMap有一个Entry内部类,功能类似Map的Entry内部类,其本身只有一个属性value,但是其继承的父类的父类
public abstract class Reference<T> {
中存在一个属性:
private T referent;
因此Entry的构造器是两个变量,Entry相当于维护了key(ThreadLocal<?>) 和 value(Object)的键值对关系。
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;
}
}
}
而threadLocals是一个全局变量, 默认为null
ThreadLocal.ThreadLocalMap threadLocals = null;
当通过getMap方法获取到的threadLocals为null时,通过createMap为threadLocals赋值
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get方法
get方法就是通过当前线程t把ThreadLocalMap中存储的对应的value拿出来。这种map的机制,保证了当前线程对应的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.
*
* @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,维护他们之间的关系,通过getMap(Thread t)方法获取ThreadLocalMap;
ThreadLocalMap存在内部类Entry,类似Map的内部类Entry,维护key(ThreadLocal<?>) 和 value(Object)的键值对关系;
ThreadLocal存在问题
问题一:
在没有使用线程池的情况下,线程退出时,系统会回调Thread类的exit方法进行一些清理工作,包括对threadLocals置null,置null的变量会被垃圾回收。
/**
* This method is called by the system to give a Thread
* a chance to clean up before it actually exits.
*/
private void exit() {
if (group != null) {
group.threadTerminated(this);
group = null;
}
/* Aggressively null out all reference fields: see bug 4006245 */
target = null;
/* Speed the release of some of these resources */
threadLocals = null;
inheritableThreadLocals = null;
inheritedAccessControlContext = null;
blocker = null;
uncaughtExceptionHandler = null;
}
但如果使用线程池,处于一直存活状态的线程不会退出,也不会进行ThreadLocal的清理工作,而你恰好给ThreadLocal设置了比较大的对象,当你再次用到这个ThreadLocal时,你会给他重新赋值,而之前设置的对象不再有用,并且不会被销毁,当这样的对象堆积的越来越多,有可能造成内存泄露。
一个比较好的习惯是,在当前线程使用完ThreadLocal时主动调用ThreadLocal.remove()方法,防止内存泄露。
问题二:
ThreadLocal不能解决新开子线程的变量传递问题,即如果你在项目中新启了多线程去跑程序,这种情况你会突然发现之前可以获取到的ThreadLocal突然都不见了,父线程传递本地变量到子线程中还需要InheritableThreadLocal等其他方式去解决。
解决方法请看:https://blog.csdn.net/qq_26012495/article/details/104379137