文章目录
ThreadLocal
ThreadLocal
文档注释:
text
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.
文档大意:这个类提供线程局部变量。这些变量与普通变量的不同之处在于,每个访问它们的线程(通过其get方法或set方法)都有自己的独立初始化的变量副本。
如文档注释所说,ThraedLocal
为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。每个访问ThreadLocal
变量的线程都有自己的隔离副本,这样防止了线程之间的干扰,消除了同步的需要。从线程的角度看,目标变量就象是线程的本地变量,这也是类名中"Local"所要表达的意思。说白了ThreadLocal
就是存放线程的局部变量的。
对比线程同步
ThreadLocal
是修饰变量的,重点是在控制变量的作用域,初衷不是为了解决线程并发和线程冲突的,而是为了让变量的种类变的更多更丰富,方便使用。很多开发语言在语言级别都提供这种作用域的变量类型。
其实要保证线程安全,并不一定就是要进行同步,两者没有因果关系。同步只是保证共享数据竞争时的手段。如果一个方法本来就不涉及共享数据,那它自然就无需任何同步措施去保证正确性。线程安全,并不一定就是要进行同步,ThreadLocal
目的是线程安全,但不是同步手段。
ThreadLocal
和线程同步机制都可以解决多线程中共享变量的访问冲突问题。在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。使用同步机制要求程序谨慎地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。而ThreadLocal
则从另一个角度来解决多线程的并发访问。ThreadLocal
会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal
提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal
。
虽然ThreadLocal
能够保证多线程访问数据安全,但是由于在每个线程中都创建了副本,所以要考虑它对资源的消耗,比如内存的占用会比不使用ThreadLocal
要大。对于多线程资源共享的问题,同步机制采用了"以时间换空间"的方式,而ThreadLocal
采用了"以空间换时间"的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
使用示例
在JDK5.0中,ThreadLocal
已经支持泛型,该类的类名已经变为ThreadLocal<T>
。API方法也相应进行了调整,新版本的API方法分别是void set(T value)
、T get()
。
ThreadLocal
中主要有三个方法:
set()
:设置当前线程的线程局部变量的值。get()
:该方法返回当前线程所对应的线程局部变量。remove()
:删除当前线程的线程局部变量,目的是为了减少内存的占用。
java
public class ThreadLocalExample {
// 创建一个 ThreadLocal 变量,用于存储每个线程独立的值
private static final ThreadLocal<String> threadLocalValue = new ThreadLocal<>();
public static void main(String[] args) {
Runnable task1 = () -> {
// 设置线程局部变量的值
threadLocalValue.set("Thread-1's Value");
// 获取并打印线程局部变量的值
System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
// 删除线程局部变量的值
threadLocalValue.remove();
System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
};
Runnable task2 = () -> {
// 设置线程局部变量的值
threadLocalValue.set("Thread-2's Value");
// 获取并打印线程局部变量的值
System.out.println(Thread.currentThread().getName() + ": " + threadLocalValue.get());
// 删除线程局部变量的值
threadLocalValue.remove();
System.out.println(Thread.currentThread().getName() + " after remove: " + threadLocalValue.get());
};
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
}
}
除此之外,ThreadLocal
提供了一个withInitial()
方法统一初始化所有线程的ThreadLocal
的值。
java
public class ThreadLocalWithInitialExample {
// 使用 withInitial 方法提供初始值
private static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static void main(String[] args) {
Runnable task1 = () -> {
// 获取并打印线程局部变量的值
SimpleDateFormat df = dateFormat.get();
String formattedDate = df.format(new Date());
System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
// 删除线程局部变量的值
dateFormat.remove();
};
Runnable task2 = () -> {
// 获取并打印线程局部变量的值
SimpleDateFormat df = dateFormat.get();
String formattedDate = df.format(new Date());
System.out.println(Thread.currentThread().getName() + ": " + formattedDate);
// 删除线程局部变量的值
dateFormat.remove();
};
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
}
}
ThreadLocal
是一种强大的工具,适用于需要线程隔离的场景,如用户会话、数据库连接和格式化对象等。使用ThreadLocal
可以有效地管理线程本地的数据,避免多线程环境下的竞争和数据一致性问题。但是由于ThreadLocal
的生命周期与线程相关,如果在线程池中使用ThreadLocal
,需要注意及时调用remove()
方法清理线程局部变量,来防止内存泄漏。
实现原理
ThreadLocal
类本身并不存储线程本地变量的值,而是通过ThreadLocalMap
来实现。每个线程内部都有一个ThreadLocalMap
实例,ThreadLocal
变量作为ThreadLocalMap
的键,存储的值是该线程对应的变量值。
set
方法首先获取当前线程 Thread
对象,然后获取该线程的 ThreadLocalMap
实例。如果存在,则将值存储在 ThreadLocalMap
中;否则,创建一个新的 ThreadLocalMap
。
java
public void set(T value) {
// 获取当前线程
Thread t = Thread.currentThread();
// 每个线程 都有一个自己的ThreadLocalMap
// ThreadLocalMap 里就保存着所有的ThreadLocal变量
ThreadLocalMap map = getMap(t);
if (map != null)
// 向map里添加值
map.set(this, value);
else
// map为null,创建一个 ThreadLocalMap
createMap(t, value);
}
// 全局定义的localMap
ThreadLocal.ThreadLocalMap threadLocals = null;
// 获取当前线程所持有的localMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
// 创建,初始化 localMap
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get
方法同样先获取当前线程 Thread
对象,然后获取该线程的 ThreadLocalMap
实例。再通过 ThreadLocal
对象作为键从 ThreadLocalMap
中获取值。如果键不存在,则调用 setInitialValue
方法初始化变量。
java
public T get() {
// 获取当前线程
Thread t = Thread.currentThread();
// 每个线程 都有一个自己的ThreadLocalMap,
// ThreadLocalMap里就保存着所有的ThreadLocal变量
ThreadLocalMap map = getMap(t);
if (map != null) {
//ThreadLocalMap的key就是当前ThreadLocal对象实例,
//多个ThreadLocal变量都是放在这个map中的
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
//从map里取出来的值就是我们需要的这个ThreadLocal变量
T result = (T)e.value;
return result;
}
}
// 如果map没有初始化,那么在这里初始化一下
return setInitialValue();
}
// 全局定义的localMap
ThreadLocal.ThreadLocalMap threadLocals = null;
// 获取当前线程所持有的localMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
setInitialValue
方法通过 initialValue
方法获取初始值,并存储在 ThreadLocalMap
中。如果 initialValue
方法未被重写,默认返回 null
。
java
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;
}
protected T initialValue() {
return null;
}
ThreadLocalMap
是一个自定义的哈希表,其中每个元素是一个Entry
对象。ThreadLocalMap
是一个比较特殊的Map
,它的每个Entry
的key
都是一个弱引用。
java
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
//key就是一个弱引用
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
这样设计的好处是,如果这个变量不再被其他对象使用时,可以自动回收这个ThreadLocal
对象,避免可能的内存泄露。
内存泄漏问题
虽然ThreadLocalMap
中的key
是弱引用,当不存在外部强引用的时候,就会自动被回收。但是Entry
中的value
依然是强引用,value
的引用链条如下:
text
Thread --> ThreadLocalMap --> Entry --> value
只有当Thread
被回收时,这个value
才有被回收的机会,否则只要线程不退出,value
总是会存在一个强引用。但是要求每个Thread
都会退出,是一个极其苛刻的要求,对于线程池来说,大部分线程会一直存在在系统的整个生命周期内,那样的话就会造成value
对象出现泄漏的可能。
如果get()
方法总是访问固定几个一直存在的ThreadLocal
,那么清理动作就不会执行,如果你没有机会调用set()
和remove()
,那么这个内存泄漏依然会发生。所以当你不需要这个ThreadLocal
变量时,主动调用remove()
,这样是能够避免内存泄漏的。可以将ThreadLocal
的使用和清理放在try-finally
块中,确保remove()
方法总是会被调用。
text
ThreadLocal<MyClass> threadLocal = new ThreadLocal<>();
try {
threadLocal.set(new MyClass());
// 使用线程局部变量
} finally {
threadLocal.remove();
}
除此之外,应尽量避免将ThreadLocal
对象声明为静态变量,特别是在应用服务器或类似环境中,因为它们的生命周期通常较长,会增加内存泄漏的风险。