ThreadLocal
提供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。通常情况下,我们创建的变量是可以被任何一个线程访问并修改的。而使用ThreadLocal创建的变量只能被当前线程访问,其他线程则无法访问和修改。 ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段(例如,用户ID或事务ID)供线程局部变量。 这些变量与普通变量不同,因为每个访问一个线程(通过其get或set方法)的线程都有其自己的,独立初始化的变量副本。
使用方法
创建对象
ThreadLocal<String> threadLocal = new ThreadLocal<>();
set()方法
threadLocal.set("HelloWorld");
get()方法
threadLocal.get();
Example:
public class ThreadLocalTest {
public static void main(String[] args) {
ThreadLocal<OperatorContext> threadLocal = new ThreadLocal<>();
Thread thread1 = new Thread(() -> {
OperatorContext operatorContext = new OperatorContext();
operatorContext.setUsername("xybh");
operatorContext.setPassword("password");
threadLocal.set(operatorContext);
while (true) {
System.out.println("thread1: " + threadLocal.get());
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
OperatorContext operatorContext = new OperatorContext();
operatorContext.setUsername("daodan");
operatorContext.setPassword("root");
threadLocal.set(operatorContext);
while (true) {
System.out.println("thread2: " + threadLocal.get());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread1.start();
thread2.start();
}
static class OperatorContext {
private String username;
private String password;
public OperatorContext() {}
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password;}
@Override
public String toString() {
return "OperatorContext{" +
"username='" + username + '\'' +
", password='" + password + '\'' +
'}';
}
}
}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}
thread1: OperatorContext{username='xybh', password='password'}
thread1: OperatorContext{username='xybh', password='password'}
thread2: OperatorContext{username='daodan', password='root'}
两个线程之间数据互不干扰
源码解析
为了更好的学习ThreadLocal的实现机制,必须去研究它的源码,了解它的实现机制。
类声明及创建方法
首先我们先看一下类的声明以及创建方法
ThreadLocal支持泛型,可以存储简单属性也可存储复合属性,常用于保存线程上下文。
public class ThreadLocal<T> {
/**
* Creates a thread local variable. The initial value of the variable is
* determined by invoking the {@code get} method on the {@code Supplier}.
*
* @param <S> the type of the thread local's value
* @param supplier the supplier to be used to determine the initial value
* @return a new thread local variable
* @throws NullPointerException if the specified supplier is null
* @since 1.8
*/
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) {
return new SuppliedThreadLocal<>(supplier);
}
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
}
ThreadLocal中有两种实现方法,一种是未带任何实现的默认构造函数,一种是通过withInitial(Supplier<? extends S> supplier)方法,可通过实现Supplier接口产生带初始值的ThreadLocal对象。
get()和set()
接下来我们再看到ThreadLocal中最重要的两个方法get()和set()
首先我们先看到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()方法的时候,会先获取到当前线程,然后获取到当前线程的ThreadLocalMap对象,如果非空,那么取出ThreadLocal的value,否则进行初始化,初始化就是将initialValue的值set到ThreadLocal中。
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;
}
当ThreadLocal未调用set()方法或者为进行初始化的情况下,调用get()方法将会调用setInitialValue方法获取默认值,未重写initialValue方法的情况下,返回值为null,我们也可以重写initialValue方法来设置默认值
public class ThreadLocalTest {
public static void main(String[] args) {
new Thread(()->{
StringThreadLocal stringThreadLocal = new StringThreadLocal();
System.out.println(stringThreadLocal.get());
}).start();
}
static class StringThreadLocal extends ThreadLocal<java.lang.String>{
@Override
protected java.lang.String initialValue() {
return "init StringThreadLocal";
}
}
}
result:
接下来我们看下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);
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set方法很简单,首先获取当前线程,再获取当前线程的ThreadLocalMap对象,如果ThreadLocalMap对象不为空,则覆盖当前ThreadLocal为key的值(并不是以当前对象为key),而当ThreadLocal为空时,则创建一个ThreadLocalMap对象将value赋值进去并将这个对象赋值给当前线程。
原理剖析
为什么ThreadLocal里的值只能单个线程使用
这个问题要看到ThreadLocalMap这个类,这个类创建后赋值给了当前线程的threadLocals,而threadLocals是每一个线程独享的,这使得ThreadLocal里的值只能单个线程使用。
public class Thread{
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocal对象存放的位置
了解过JMM的同学应该清楚,在Java中,栈内存归属于单个线程,每个线程都会有一个栈内存,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存。可能很多同学会认为应该ThreadLocal的其他线程不可见,那么它应该是存在在栈内存中,其实不然,因为ThreadLocal实例实际上也是被其创建的类持有(更顶端应该是被线程持有)。而ThreadLocal的值其实也是被线程实例持有。只不过它使用了一些技巧使其对其他线程不可见,但它还是存放在堆内存中的。
ThreadLocal内存泄漏问题
之前阅读过一些文章,说ThreadLocal存在内存泄漏问题,在使用完ThreadLocal后需要手动将其关闭,这里我们通过阅读源码发现:真正用来存储值的对象是ThreadLocalMap中的Entry,而它是一个弱引用对象。
弱引用: 在垃圾回收器线程扫描它所管辖的内存区域的过程中,一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。
如果一个对象是偶尔(很少)的使用,并且希望在使用时随时就能获取到,但又不想影响此对象的垃圾收集,那么你应该用Weak Reference来记住此对象。
因为ThreadLocalMap在选择key的时候,并不是直接选择ThreadLocal实例,而是ThreadLocal实例的弱引用。所以实际上从ThreadLocal设计角度来说是不会导致内存泄露的。
参考
【1】理解Java中的ThreadLocal——技术小黑屋
【2】ThreadLocal详解——神一般的存在