ThreadLocal

787 阅读3分钟

ThreadLocal定义

java中的ThreadLocal,顾名思义就是线程本地信息。官网给出的定义如下:

  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.  {@code ThreadLocal} instances are typically private
  static fields in classes that wish to associate state with a thread (e.g.,
 a user ID or Transaction ID).

ThreadLocal的方法

 public T get()
 public void set(T value) 
 public void remove()

以set方法为例,看下具体实现

   /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
     public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

这里又出现一个ThreadLocalMap类,通过getMap方法得到,参数为当前线程t

     /**
     * Get the map associated with a ThreadLocal. Overridden in
     * InheritableThreadLocal.
     *
     * @param  t the current thread
     * @return the map
     */
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

这个getMap方法获取的就是当前线程的局部变量threadLocals,其定义如下:

 /* ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. */
    ThreadLocal.ThreadLocalMap threadLocals = null;

所以ThreadLocal类的get/set方法,其实都是首先获取当前线程t,然后根据线程t得到线程的ThreadLocalMap变量,接下来就是对这个线程变量进行操作,这里才明白什么叫做线程局部变量。

ThreadLocalMap

 /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */

其实ThreadLocalMap就是一个自定义实现的HashMap,他的key就是ThreadLocal类型的变量 ThreadLocal的set方法会调用ThreadLocalMap的方法,看下方法的定义

private void set(ThreadLocal<?> key, Object value)

这里可以看出一个Thread可以存储不同类型的数据。

總結下來就是下面這張圖:

static

why static

这里注意一点引起迷惑的就是明明是每个线程本地的信息,为什么又要定义成 static field,这不是有点矛盾么?而且为什么又是 typically,那非typically呢?

static变量是类静态变量,即一个线程内,static变量是被各个实例共同引用的, 如果把ThreadLocal声明为非static变量,那么每创建一个该类的实例就会创建一个新的实例对象,那么同一个线程就会访问到同一个实例对象的不同ThreadLocal对象,而分析ThreadLocal类可知,事实上ThreadLocal本身是不存储数据的,只是对Thread类对象的局部变量进行操作,所以定义为static更合适,否则虽然不会导致错误,也会导致资源浪费。

举例如下, 如果设置为static型变量

public class ThreadLocalUtils {
  private static ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

  public static String getThreadLocal() {
    return stringThreadLocal.get();
  }

  public static void setThreadLocal(String value) {
    stringThreadLocal.set(value);
  }

  public static void main(String[] args) {
    List<Integer> list = new LinkedList<>();

    for (int i=0; i<4; i++) {
      list.add(i);
    }

    list.parallelStream().forEach(integer -> {
      ThreadLocalUtils.setThreadLocal(String.valueOf(integer));
      System.out.println("Start-"+Thread.currentThread().getName()+":"+ThreadLocalUtils.getThreadLocal());

      try {
        Thread.sleep(900);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("End-"+Thread.currentThread().getName()+":" + ThreadLocalUtils.getThreadLocal());
    });
  }
}

输出结果为:

Start-ForkJoinPool.commonPool-worker-2:3
Start-ForkJoinPool.commonPool-worker-1:1
Start-ForkJoinPool.commonPool-worker-3:0
Start-main:2
End-main:2
End-ForkJoinPool.commonPool-worker-3:0
End-ForkJoinPool.commonPool-worker-2:3
End-ForkJoinPool.commonPool-worker-1:1

如果设置为非static类型变量:

public class ThreadLocalUtils {
  private ThreadLocal<String> stringThreadLocal = new ThreadLocal<>();

  public  String getThreadLocal() {
    return stringThreadLocal.get();
  }

  public void setThreadLocal(String value) {
    stringThreadLocal.set(value);
  }

  public static void main(String[] args) {
    List<Integer> list = new LinkedList<>();
    for (int i=0; i<4; i++) {
      list.add(i);
    }

    list.parallelStream().forEach(integer -> {
      ThreadLocalUtils utils = new ThreadLocalUtils();
      utils.setThreadLocal(String.valueOf(integer));
      System.out.println("Start-"+Thread.currentThread().getName()+":"+utils.getThreadLocal());

      try {
        Thread.sleep(900);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.println("End-"+Thread.currentThread().getName()+":" + utils.getThreadLocal());
    });
  }
}

输出如下:

Start-main:2
Start-ForkJoinPool.commonPool-worker-2:3
Start-ForkJoinPool.commonPool-worker-3:0
Start-ForkJoinPool.commonPool-worker-1:1
End-main:2
End-ForkJoinPool.commonPool-worker-1:1
End-ForkJoinPool.commonPool-worker-2:3
End-ForkJoinPool.commonPool-worker-3:0

两者的输出结果完全相同,但是为非static型变量就会出现类变量的重复定义。

如果将stringThreadLocal变为String类型变量就会变成

Start-main:3
Start-ForkJoinPool.commonPool-worker-1:1
Start-ForkJoinPool.commonPool-worker-3:0
Start-ForkJoinPool.commonPool-worker-2:3
End-ForkJoinPool.commonPool-worker-3:1
End-main:1
End-ForkJoinPool.commonPool-worker-1:1
End-ForkJoinPool.commonPool-worker-2:1

可以看到ThreadLocal变量和非ThreadLocal变量的区别了。

when not static

那么非typically的场景是什么呢,就是在单例模式下,不需要指定为static:让类本身成为一个单例对象,这样只要全局中有可用的单例对象,就可以安全地使用实例级ThreadLocal。