ThreadLocal

ThreadLocal 不是用来解决共享变量问题的,它与多线程的并发问题没有任何关系。

1.简介

  早在 JDK 1.2 的版本中就提供Java.lang.ThreadLocal,1.5 开始,ThreadLocal 开始支持泛型。ThreadLocal 为解决多线程程序的并发问题提供了一种新的思路。使用这个工具类可以很简洁地编写出优美的多线程程序。
  当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
  从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。

2.用法

  • ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。
  • ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

3.原理

3.1 线程共享变量缓存

Thread.ThreadLocalMap<ThreadLocal, Object>

  • Thread: 当前线程,可以通过Thread.currentThread()获取。
  • ThreadLocal:我们的 static ThreadLocal变量。
  • Object: 当前线程共享变量。

我们调用 ThreadLocal.get 方法时,实际上是从当前线程中获取 ThreadLocalMap<ThreadLocal, Object>,然后根据当前 ThreadLocal 获取当前线程共享变量 Object
ThreadLocal.set,ThreadLocal.remove实际上是同样的道理。

3.2 这种存储结构的好处

  1. 线程死去的时候,线程共享变量 ThreadLocalMap 则销毁。
  2. ThreadLocalMap 键值对数量为 ThreadLocal 的数量,一般来说 ThreadLocal数量很少,相比在ThreadLocal中用 Map 键值对存储线程共享变量(Thread数量一般来说比ThreadLocal数量多),性能提高很多

3.3 ThreadLocalMap弱引用问题

当线程没有结束,但是 ThreadLocal 已经被回收,则可能导致线程中存在ThreadLocalMap<null, Object>的键值对,造成内存泄露。(ThreadLocal被回收,ThreadLocal关联的线程共享变量还存在)。
虽然ThreadLocal的get,set方法可以清除ThreadLocalMap中key为null的value,但是get,set方法在内存泄露后并不会必然调用,所以为了防止此类情况的出现,我们有两种手段。

  • 1.使用完线程共享变量后,显式调用ThreadLocalMap.remove方法清除线程共享变量;
  • 2.JDK建议 ThreadLocal 定义为private static,这样ThreadLocal的弱引用问题则不存在了。

4.使用示例

public class Run {  
    private static ThreadLocal tl = new ThreadLocal();

    public static void main(String[] args) {
        if (tl.get() == null) {
            System.out.println("从未放过值");
            tl.set("我的值");
        }
        System.out.println(tl.get());
        System.out.println(tl.get());
    }

}