ThreadLocal常见面试题

为什么要使用ThreadLocal?

并发编程是一项非常重要的技术,它让我们的程序变得更加高效。但在并发的场景中,如果有多个线程同时修改公共变量,可能会出现线程安全问题,即该变量最终结果可能出现异常。

为了解决线程安全问题,JDK出现了很多技术手段,比如:使用synchronizedLock,给访问临界资源的代码加锁,保证了代码的原子性

但在高并发的场景中,如果多个线程同时竞争一把锁,这时会存在大量的锁等待,可能会浪费很多时间,让系统的响应时间一下子变慢。

因此,JDK还提供了另外一种用空间换时间的新思路:ThreadLocal。(为对象分配内存时的TLAB分配过程也是这个思想)。

它的核心思想是:共享变量在每个线程都有一个副本,每个线程操作的都是自己的副本,对其他线程没有影响。

@Service
public class ThreadLocalService {
    private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

    public void add() {
        threadLocal.set(1);
        doSamething();
        Integer integer = threadLocal.get();
    }
}

ThreadLocal的原理是什么? 

为了搞清楚ThreadLocal的底层实现原理,我们不得不扒一下源码。

ThreadLocal的内部有一个静态的内部类叫:ThreadLocalMap

public class ThreadLocal<T> {

    // 省略很多代码......

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    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);
        }
        if (this instanceof TerminatingThreadLocal) {
            TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
        }
        return value;
    }

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }

    // 省略很多代码......
}

ThreadLocal的get方法、set方法和setInitialValue方法,其实最终操作的都是ThreadLocalMap类中的数据。其中ThreadLocalMap类的内部如下:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
   }
   ...
   private Entry[] table;
   ...
}

ThreadLocalMap 里面包含一个静态的内部类 Entry,该类继承于 WeakReference 类。其中Entry的key,就是ThreadLocal,被定义成了弱引用,value就是要存储的值。ThreadLocalMap 内部还包含了一个Entry数组,而ThreadLocalMap被定义成了Thread类的成员变量。

public class Thread implements Runnable {
    ...
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

 用一张图总结一下引用关系:

如果ThreadLocal被定义成了静态变量的话,还会被定义ThreadLocal所在类的Class对象所引用。

ThreadLocalMap为什么要用ThreadLocal做key,而不是用Thread做key?

如果在你的应用中,一个线程中只使用了一个 ThreadLocal 对象,那么使用 Thread 做key也未尝不可。但实际情况中,你的应用,一个线程中很有可能不只使用了一个 ThreadLocal 对象。这时使用 Thread 做key不就出有问题?

@Service
public class ThreadLocalService {
    private static final ThreadLocal<Integer> threadLocal1 = new ThreadLocal<>();
    private static final ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
    private static final ThreadLocal<Integer> threadLocal3 = new ThreadLocal<>();
}    

假如使用Thread做key时,你的代码中定义了3个ThreadLocal对象,那么通过Thread对象,它怎么知道要获取哪个ThreadLocal对象呢? 

图片

因此,不能使用 Thread 做key,而应该改成用 ThreadLocal 对象做key,这样才能通过具体ThreadLocal对象的get方法,轻松获取到你想要的ThreadLocal对象。 

图片

Entry的key为什么设计成弱引用?

Entry的key是ThreadLocal对象,使用了 WeakReference 对象,即被设计成了弱引用 (弱引用的对象,在GC做垃圾清理的时候,就会被自动回收)。

那么,为什么要这样设计? 

假如 key 对 ThreadLocal 对象的弱引用,改为强引用。

图片

ThreadLocal变量对ThreadLocal对象是有强引用存在的,即使ThreadLocal变量生命周期完了,设置成null了,但由于key对ThreadLocal还是强引用。此时,如果执行该代码的线程使用了线程池,一直长期存在,不会被销毁。

就会存在这样的强引用链:Thread变量 -> Thread对象 -> ThreadLocalMap -> Entry -> key -> ThreadLocal对象。那么,ThreadLocal对象和ThreadLocalMap都将不会被 gc 回收,于是产生了内存泄露问题。

为了解决这个问题,JDK的开发者们把Entry的key设计成了弱引用。如果key是弱引用,当ThreadLocal变量指向null之后,在GC做垃圾清理的时候,key会被自动回收,其值也被设置成null。

图片

由于当前的ThreadLocal变量已经被指向null了,但如果直接调用它的get、set或remove方法,很显然会出现空指针异常。因为它的生命已经结束了,再调用它的方法也没啥意义。

此时,如果系统中还定义了另外一个ThreadLocal变量b,调用了它的get、set或remove,三个方法中的任何一个方法,都会自动触发清理机制,将key为null的value值清空

如果key和value都是null,那么Entry对象会被GC回收。如果所有的Entry对象都被回收了,ThreadLocalMap也会被回收了。

这样就能最大程度的解决内存泄露问题

此外,你可能还会问这样一个问题:Entry的value为什么不设计成弱引用?

答:Entry的value假如只是被Entry引用,有可能没被业务系统中的其他地方引用。如果将value改成了弱引用,被GC贸然回收了(数据突然没了),可能会导致业务系统出现异常。

而相比之下,Entry的key,管理的地方就非常明确了。

这就是Entry的key被设计成弱引用,而value没被设计成弱引用的原因。

ThreadLocal真的会导致内存泄露?

通过上面的Entry对象中的key设置成弱引用,并且使用get、set或remove方法清理key为null的value值,就能彻底解决内存泄露问题?

答案是否定的。

图片

假如ThreadLocalMap中存在很多key为null的Entry,但后面的程序,一直都没有调用过ThreadLocal的get、set或remove方法。那么,Entry的value值一直都没被清空。

所以会存在这样一条强引用链:Thread变量 -> Thread对象 -> ThreadLocalMap -> Entry -> value -> Object。其结果就是:Entry和ThreadLocalMap将会长期存在下去,会导致内存泄露。

如何解决内存泄露问题?

前面说过的ThreadLocal还是会导致内存泄露的问题,我们有没有解决办法呢?

答:有办法,调用ThreadLocal对象的remove方法。不是在一开始就调用remove方法,而是在使用完ThreadLocal对象之后。

列如:先创建一个CurrentUser类,其中包含了ThreadLocal的逻辑。

public class CurrentUser {
    private static final ThreadLocal<UserInfo> THREA_LOCAL = new ThreadLocal();
    
    public static void set(UserInfo userInfo) {
        THREA_LOCAL.set(userInfo);
    }
    
    public static UserInfo get() {
       THREA_LOCAL.get();
    }
    
    public static void remove() {
       THREA_LOCAL.remove();
    }
}

 然后在业务代码中调用相关方法:

public void doSamething(UserDto userDto) {
   UserInfo userInfo = convert(userDto);
   
   try{
     CurrentUser.set(userInfo);
     ...
     
     //业务代码
     UserInfo userInfo = CurrentUser.get();
     ...
   } finally {
      CurrentUser.remove();
   }
}

需要我们特别注意的地方是:一定要在finally代码块中,调用remove方法清理没用的数据。如果业务代码出现异常,也能及时清理没用的数据。remove方法中会把Entry中的key和value都设置成null,这样就能被GC及时回收,无需触发额外的清理机制,所以它能解决内存泄露问题。 

ThreadLocal是如何get数据的?

get方法的源码

public T get() {
    // 当前线程
    Thread t = Thread.currentThread();
    // 拿到线程中定义的ThreadLocalMap
    ThreadLocalMap map = getMap(t);
    // map已初始化
    if (map != null) {
        // 已当前ThreadLocal为key,从map获取entry
        ThreadLocalMap.Entry e = map.getEntry(this);
        // 若存在null,则返回value
        if (e != null) {
            T result = (T)e.value;
            return result;
        }
    }
    // map未初始化 或者get到的value为null
    return setInitialValue();
}
private Entry getEntry(ThreadLocal<?> key) {
    // 计算数组下标
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    // entry不为null,且key也和entry里的key相同
    if (e != null && e.refersTo(key))
        // 说明找到数据了,直接返回
        return e;
    else
        // 说明出现hash冲突了,继续往后找
        // 并且会清除掉key为null的entry
        return getEntryAfterMiss(key, i, e);
}
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
    Entry[] tab = table;
    int len = tab.length
    // entry不为null,则一直往后找
    while (e != null) {
        // 当前Entry的key正好是我们所需要寻找的key
        if (e.refersTo(key))
            return e;
        // entry的key为null,说明不存在要找的数据,则清理脏数据
        if (e.refersTo(null))
            // 先将entry的value置为null,再将entry置为null
            // 然后继续往后判断entry
            // 若entry为null,则方法退出
            // 若entry不为null,但key为null,则清除该entry
            // 若entry不为null,key也不为null,则判断需不需要重新定位该entry的存放位置
            expungeStaleEntry(i);
        // 则继续往后找    
        else
            i = nextIndex(i, len);
        e = tab[i];
    }
    return null;
}

ThreadLocal从数组中找数据的大致过程:

  1. 通过key的hashCode取余计算出一个下标;
  2. 通过下标,在数组中定位具体Entry,如果key正好是我们所需要的key,说明找到了,则直接返回数据;
  3. 如果该entry不是我们想要的数据,则从数组的下标位置,继续往后面找,找到则返回数据;
  4. 如果找到最后一个位置,还是没有找到数据,则再从头,即下标为0的位置,继续从前往后找数据;
  5. 直到找到第一个entry为null或者entry的key为null为止。

ThreadLocal是如何set数据和扩容的? 

在set方法中会调用rehash方法: 

private void set(ThreadLocal<?> key, Object value) {
    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1)
    // 从i位置开始往后遍历,找空位置
    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        // 数据已存在,则覆盖数据    
        if (e.refersTo(key)) {
            e.value = value;
            return;
        }
        // entry的key为null
        if (e.refersTo(null)) {
            // 将e的value置为null,以便于gc回收
            // 将key,value放置在该位置
            replaceStaleEntry(key, value, i);
            return;
        }
    
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // 清理一些过期的数据,如果有清理掉一些过期数据,则返回
    // 如果没有过期数据可以清理,则判断元素个数是否达到扩容阈值,若达到则尝试扩容
    // threshold = 数组长度的2/3
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        // 尝试扩容
        rehash();
}
// 在真正扩容之前,先尝试回收所有key为null的entry,腾出一些空间。
// 如果回收之后的size大于等于threshold的3/4时,才需要真正的扩容。
private void rehash() {
    // 清除数组中所有过期的数据(即key为null的entry)
    expungeStaleEntries()
    // threshold = 数组长度的2/3
    // 清理过后再次判断数组元素是否达到阈值的3/4
    // 若数组长度为16,则threshold = 10 
    // threshold - threshold / 4 = 8
    // 即添加数据后,数组中元素个数达到数组长度的一半时,则需要扩容
    if (size >= threshold - threshold / 4)
        // 真正的扩容
        resize();
}
// 数组扩容
private void resize() {    
    Entry[] oldTab = table;             // 老数组
    int oldLen = oldTab.length;         // 老数组长度
    int newLen = oldLen * 2;            // 新的数组长度,为老数组的2倍
    Entry[] newTab = new Entry[newLen]; // 新数组
    int count = 0
    // 遍历老数组,将数据数据赋值到新数组
    for (Entry e : oldTab) {
        if (e != null) {
            ThreadLocal<?> k = e.get();
            if (k == null) {
                e.value = null;
            } else {
                int h = k.threadLocalHashCode & (newLen - 1);
                while (newTab[h] != null)
                    h = nextIndex(h, newLen);
                newTab[h] = e;
                count++;
            }
        }
    }
    // 设置新的阈值
    setThreshold(newLen);
    size = count;
    table = newTab;
}

set数据和扩容的主要过程:

  1. 添加数据后将size加1;

  2. 从数组当前位置往后检查部分槽位上是否存在key为null的entry(检查个数跟size大小有关),若存在,则清理脏数据,不需要考虑扩容;

  3. 若没有检测到脏数据,则判断szie是否达到扩容阈值(扩容阈值为数组长度的2/3)。若达到,则需要考虑扩容;

  4. 扩容前先尝试清除数组中所有的脏数据,腾出一些空间;

  5. 回收之后,若size大于等于数组长度的一半时,才需要真正的扩容;

  6. 扩容的新数组长度为原数组长度的2倍。

父子线程如何共享数据?

使用 InheritableThreadLocal,它是JDK自带的类,继承了ThreadLocal类。

public class ThreadLocalTest {

    public static void main(String[] args) {
        InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
        threadLocal.set(6);
        System.out.println("父线程获取数据:" + threadLocal.get());

        new Thread(() -> {
            System.out.println("子线程获取数据:" + threadLocal.get());
        }).start();
    }
}
父线程获取数据:6
子线程获取数据:6

其实,在Thread类中除了成员变量threadLocals之外,还有另一个成员变量:inheritableThreadLocals。

ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;

InheritableThreadLocal 使用的是Thread的inheritableThreadLocals 这个ThreadLocalMap ,在它的 init方法 中会将父线程中往ThreadLocal设置的值,拷贝一份到子线程中。

线程池中如何共享数据?

在真实的业务场景中,一般很少用单独的线程,绝大多数,都是用的线程池。那么,在线程池中如何共享ThreadLocal对象生成的数据呢?因为涉及到不同的线程,如果直接使用ThreadLocal,显然是不合适的。

我们应该使用InheritableThreadLocal,具体代码如下:

private static void fun1() {
    InheritableThreadLocal<Integer> threadLocal = new InheritableThreadLocal<>();
    threadLocal.set(6);
    System.out.println("父线程获取数据:" + threadLocal.get());

    ExecutorService executorService = Executors.newSingleThreadExecutor();

    threadLocal.set(6);
    executorService.submit(() -> {
        System.out.println("第一次从线程池中获取数据:" + threadLocal.get());
    });

    threadLocal.set(7);
    executorService.submit(() -> {
        System.out.println("第二次从线程池中获取数据:" + threadLocal.get());
    });
}
父线程获取数据:6
第一次从线程池中获取数据:6
第二次从线程池中获取数据:6

由于这个例子中使用了单例线程池,固定线程数是1。

第一次submit任务的时候,该线程池会自动创建一个线程。因为使用了InheritableThreadLocal,所以创建线程时,会调用它的init方法,将父线程中的inheritableThreadLocals数据复制到子线程中。所以我们看到,在主线程中将数据设置成6,第一次从线程池中获取了正确的数据6。

之后,在主线程中又将数据改成7,但在第二次从线程池中获取数据却依然是6。

因为第二次submit任务的时候,线程池中已经有一个线程了,就直接拿过来复用,不会再重新创建线程了。所以不会再调用线程的init方法,所以第二次其实没有获取到最新的数据7,还是获取的老数据6。

那么,这该怎么办呢?

答:使用 TransmittableThreadLocal,它并非JDK自带的类,而是阿里巴巴开源jar包中的类。

可以通过如下pom文件引入该jar包:

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>transmittable-thread-local</artifactId>
   <version>2.11.0</version>
   <scope>compile</scope>
</dependency>
private static void fun2() {
    TransmittableThreadLocal<Integer> threadLocal = new TransmittableThreadLocal<>();
    threadLocal.set(6);
    System.out.println("父线程获取数据:" + threadLocal.get());
    ExecutorService ttlExecutorService = TtlExecutors.getTtlExecutorService(Executors.newFixedThreadPool(1));

    threadLocal.set(6);
    ttlExecutorService.submit(() -> System.out.println("第一次从线程池中获取数据:" + threadLocal.get()));

    threadLocal.set(7);
    ttlExecutorService.submit(() -> System.out.println("第二次从线程池中获取数据:" + threadLocal.get()));
    
    threadLocal.set(8);
    ttlExecutorService.submit(() -> System.out.println("第二次从线程池中获取数据:" + threadLocal.get()));
}
父线程获取数据:6
第一次从线程池中获取数据:6
第二次从线程池中获取数据:7
第二次从线程池中获取数据:8

你可能会发现,代码中除了使用TransmittableThreadLocal类之外,还使用了TtlExecutors.getTtlExecutorService方法,去创建ExecutorService对象。

这是非常重要的地方,如果没有这一步,TransmittableThreadLocal在线程池中共享数据将不会起作用。 

创建ExecutorService对象,底层的submit方法会将任务封装成TtlRunnable或TtlCallable对象。

TtlRunnable类为例,它实现了Runnable接口,同时还实现了它的run方法:

public void run() {
    Map<TransmittableThreadLocal<?>, Object> copied = (Map)this.copiedRef.get();
    if (copied != null && (!this.releaseTtlValueReferenceAfterRun || this.copiedRef.compareAndSet(copied, (Object)null))) {
        Map backup = TransmittableThreadLocal.backupAndSetToCopied(copied);

        try {
            this.runnable.run();
        } finally {
            TransmittableThreadLocal.restoreBackup(backup);
        }
    } else {
        throw new IllegalStateException("TTL value reference is released after run!");
    }
}

这段代码的主要逻辑如下:

  1. 把当时的ThreadLocal做个备份,然后将父类的ThreadLocal拷贝过来。
  2. 执行真正的run方法,可以获取到父类最新的ThreadLocal数据。
  3. 从备份的数据中,恢复当时的ThreadLocal数据。 

ThreadLocal有哪些使用场景?

  • 在spring事务中,保证一个线程下,一个事务的多个操作拿到的是一个Connection。
  • 在JDK8之前,为了解决SimpleDateFormat的线程安全问题。
  •  获取当前登录用户上下文。
  • 临时保存权限数据。

ThreadLocal变量为什么建议要定义成static的?

ThreadLocal 的原理是在Thread内部有一个ThreadLocalMap的集合对象,他的key是ThreadLocal,value就是你要存储的变量副本,不同线程的 ThreadLocalMap 是隔离开的,如果ThreadLocal 变量是非 static 的话,就会使得每次生成实例都要生成不同的 ThreadLocal 对象,虽然这样程序不会有什么异常,但是会造成内存资源的浪费

但是ThreadLocal 变量定义成staic也有一定的缺点,就是在线程池的情况下更容易造成内存泄露,在使用完ThreadLocal后要调用其remove方法,手动清理失效的Entry对象,避免内存泄漏。

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
面试题包括以下十九部分:Java 基础、容器、多线程、反射、对象拷贝、Java Web 模块、异常、网络、设计模式、Spring/Spring MVC、Spring Boot/Spring Cloud、Hibernate、Mybatis、RabbitMQ、Kafka、Zookeeper、MySql、Redis、JVM 。 目录: 一、Java 基础 1.JDK 和 JRE 有什么区别? 2.== 和 equals 的区别是什么? 3.两个对象的 hashCode()相同,则 equals()也一定为 true,对吗? 4.final 在 java 中有什么作用? 5.java 中的 Math.round(-1.5) 等于多少? 6.String 属于基础的数据类型吗? 7.java 中操作字符串都有哪些类?它们之间有什么区别? 8.String str="i"与 String str=new String(“i”)一样吗? 9.如何将字符串反转? 10.String 类的常用方法都有那些? 11.抽象类必须要有抽象方法吗? 12.普通类和抽象类有哪些区别? 13.抽象类能使用 final 修饰吗? 14.接口和抽象类有什么区别? 15.java 中 IO 流分为几种? 16.BIO、NIO、AIO 有什么区别? 17.Files的常用方法都有哪些? 二、容器 18.java 容器都有哪些? 19.Collection 和 Collections 有什么区别? 20.List、Set、Map 之间的区别是什么? 21.HashMap 和 Hashtable 有什么区别? 22.如何决定使用 HashMap 还是 TreeMap? 23.说一下 HashMap 的实现原理? 24.说一下 HashSet 的实现原理? 25.ArrayList 和 LinkedList 的区别是什么? 26.如何实现数组和 List 之间的转换? 27.ArrayList 和 Vector 的区别是什么? 28.Array 和 ArrayList 有何区别? 29.在 Queue 中 poll()和 remove()有什么区别? 30.哪些集合类是线程安全的? 31.迭代器 Iterator 是什么? 32.Iterator 怎么使用?有什么特点? 33.Iterator 和 ListIterator 有什么区别? 34.怎么确保一个集合不能被修改? 三、多线程 35.并行和并发有什么区别? 36.线程和进程的区别? 37.守护线程是什么? 38.创建线程有哪几种方式? 39.说一下 runnable 和 callable 有什么区别? 40.线程有哪些状态? 41.sleep() 和 wait() 有什么区别? 42.notify()和 notifyAll()有什么区别? 43.线程的 run()和 start()有什么区别? 44.创建线程池有哪几种方式? 45.线程池都有哪些状态? 46.线程池中 submit()和 execute()方法有什么区别? 47.在 java 程序中怎么保证多线程的运行安全? 48.多线程锁的升级原理是什么? 49.什么是死锁? 50.怎么防止死锁? 51.ThreadLocal 是什么?有哪些使用场景? 52.说一下 synchronized 底层实现原理? 53.synchronized 和 volatile 的区别是什么? 54.synchronized 和 Lock 有什么区别? 55.synchronized 和 ReentrantLock 区别是什么? 56.说一下 atomic 的原理? 四、反射 57.什么是反射? 58.什么是 java 序列化?什么情况下需要序列化? 59.动态代理是什么?有哪些应用? 60.怎么实现动态代理? 五、对象拷贝 61.为什么要使用克隆? 62.如何实现对象克隆? 63.深拷贝和浅拷贝区别是什么? 六、Java Web 64.jsp 和 servlet 有什么区别? 65.jsp 有哪些内置对象?作用分别是什么? 66.说一下 jsp 的 4 种作用域? 67.session 和 cookie 有什么区别? 68.说一下 session 的工作原理? 69.如果客户端禁止 cookie 能实现 session 还能用吗? 70.spring mvc 和 struts 的区别是什么? 71.如何避免 sql 注入? 72.什么是 XSS 攻击,如何避免? 73.什么是 CSRF 攻击,如何避免? 七、异常 74.throw 和 throws 的区别? 75.final、finally、finalize 有什么区别? 76.try-catch-finally 中哪个部分
ThreadLocalJava中的一个关键字,它主要用于实现线程局部变量。每个线程都可以拥有自己的ThreadLocal变量,线程之间互不干扰。ThreadLocal的使用可以避免多线程环境下的数据共享问题,提高线程安全性。 关于ThreadLocal面试题回答可以从以下几个方面展开: 1. 简单介绍ThreadLocal的作用和原理。 ThreadLocal的作用是提供线程局部变量,每个线程都可以拥有自己的ThreadLocal变量,这些变量在不同的线程之间互不干扰。ThreadLocal通过使用ThreadLocalMap来存储每个线程的变量副本,每个线程都有自己的ThreadLocalMap实例。当获取ThreadLocal变量时,实际上是通过当前线程获取自己的ThreadLocalMap,然后从中获取对应的变量值。 2. 如何使用ThreadLocal?举例说明。 使用ThreadLocal可以通过以下步骤: - 创建ThreadLocal对象。 - 在需要使用线程局部变量的地方,通过ThreadLocal的get()方法获取变量值,如果没有设置过,则返回null。 - 在需要设置线程局部变量的地方,通过ThreadLocal的set()方法设置变量值。 - 在不需要使用线程局部变量的时候,可以通过ThreadLocal的remove()方法将其从当前线程中删除。 例如,可以使用ThreadLocal来实现一个简单的计数器: ```java public class Counter { private static ThreadLocal<Integer> counter = new ThreadLocal<>(); public static void increase() { counter.set(counter.get() + 1); } public static void reset() { counter.set(0); } public static int getCount() { return counter.get(); } } ``` 3. ThreadLocal的使用场景有哪些? ThreadLocal的使用场景包括但不限于: - 在多线程环境下,每个线程需要独立拥有自己的变量副本,避免数据共享问题。 - 在Web应用中,每个请求都可以通过ThreadLocal来保存一些请求相关的信息,如用户登录信息、请求参数等,方便在整个请求处理过程中进行访问。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值