对于Java程序员来说,相信对于ThreadLocal并不陌生。ThreadLocal是线程本地变量的意思,主要用于多线程对同一个变量的读写操作,且相互之间又不会依赖于原始值的改变而影响线程的业务逻辑。主要表现为以下两个方面:

  1. 每一个线程对ThreadLocal变量都保存着一份副本,任何一个线程的操作只是对这个副本的操作,并不会对原始数据进行修改。
  2. 由于ThreadLocal变量是线程本地变量,因此其不适用于值的改变需要对多线程必须可见的场景。

ThreadLocal的原理

首先简单介绍一下ThreadLocal的原理。ThreadLocal是线程本地变量,在每一个Thread中都维护者一个关联ThreadLocal的变量。如下图所示:

ThreadLocal的原理及正确使用方法

Thread中ThreadLocal关联属性

每个线程中都有一个ThreadLocalMap的变量,其存储的就是该线程的本地变量。接下来我们再看一下ThreadLocalMap的结构:

ThreadLocal的原理及正确使用方法

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存在的问题,我会在下一篇文章中进行分析。

版权声明:本文为匿名原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: