ThreadLocal的原理及正确使用方法
对于Java程序员来说,相信对于ThreadLocal并不陌生。ThreadLocal是线程本地变量的意思,主要用于多线程对同一个变量的读写操作,且相互之间又不会依赖于原始值的改变而影响线程的业务逻辑。主要表现为以下两个方面:
- 每一个线程对ThreadLocal变量都保存着一份副本,任何一个线程的操作只是对这个副本的操作,并不会对原始数据进行修改。
- 由于ThreadLocal变量是线程本地变量,因此其不适用于值的改变需要对多线程必须可见的场景。
ThreadLocal的原理
首先简单介绍一下ThreadLocal的原理。ThreadLocal是线程本地变量,在每一个Thread中都维护者一个关联ThreadLocal的变量。如下图所示:
Thread中ThreadLocal关联属性
每个线程中都有一个ThreadLocalMap的变量,其存储的就是该线程的本地变量。接下来我们再看一下ThreadLocalMap的结构:
ThreadLocalMap对象
如上图所示,ThreadLocalMap本身就是一个Map,其中Map的key是ThreadLocal对象,而value则是ThreadLocal中存储的对象。除此之外,Map的key继承了WeakRefence,也就是说ThreadLocalMap的key是一个弱依赖,如果GC Root不可达的情况下,在下一次Java GC时会被回收。
ThreadLocal的使用方法
ThreadLocal主要提供了以下几个方法:
1. public T get() 2. public void set(T value) 3. public void remove() 4. public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier)
- get():线程获取该ThreadLocal对应的变量
- set(T value):线程设置该ThreadLocal对应的变量
- remove():线程移除该ThreadLocal
- withInitial:这个一般在生成ThreadLocal时使用初始化变量
根据Java Doc的建议,ThreadLocal一般声明为static变量:
public class ThreadLocalTest {
/**
* 未初始化的本地线程变量
*/
private static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
/**
* 带有初始值的本地线程变量
*/
private static ThreadLocal<User> userThreadLocalWithInit = ThreadLocal.withInitial(() -> new User(1L, "lanxing", "1x"));
public static void main(String[] args) throws InterruptedException {
int threadNum = 10;
List<Thread> threadList = Lists.newArrayList();
User user11 = userThreadLocal.get();
for (int i = 0; i < threadNum; ++i){
Thread t = new Thread(() -> {
User user1 = userThreadLocal.get();
User user = userThreadLocalWithInit.get(); //获取变量
userThreadLocalWithInit.set(new User(2L, "lanxing1", "2x")); //重新设置变量
userThreadLocalWithInit.remove(); //移除ThreadLocal变量
});
threadList.add(t);
t.start();
}
for (int i = 0; i < threadNum; ++i){
threadList.get(i).join();
}
}
}
特别需要注意的是,如果初始化ThreadLocal时没有使用withInitial初始化初始值,那么在get()时会返回空,在使用基本类型(long,int等)时需要特别注意因为装包拆包而造成的空指针问题。
相信阅读到这里,大家对ThreadLocal的基本原理及使用方法都有了一些了解,对于ThreadLocal存在的问题,我会在下一篇文章中进行分析。