ThreadLocal是什么?

ThreadLocal提供线程本地变量,每个现场拥有本地变量的副本,各个线程之间的变量互不干扰。ThreadLocal实现在多线程环境下去保证变量的安全。
This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.

ThreadLocal的使用

JDK中的例子,定一个私有的,静态的ThreadLocal变量,并且重写了initialValue()方法,每个线程都会调用这个方法,获得初始化值。

  1. public class ThreadId {
  2. // Integer类型的原子类,用来分配Id,保证其本身是线程安全的
  3. // Atomic integer containing the next thread ID to be assigned
  4. private static final AtomicInteger nextId = new AtomicInteger(0);
  5. // 包含每个线程 ID 的线程局部变量
  6. // Thread local variable containing each thread's ID
  7. private static final ThreadLocal<Integer> threadId =
  8. new ThreadLocal<Integer>() {
  9. @Override
  10. protected Integer initialValue() {
  11. return nextId.getAndIncrement();
  12. }
  13. };
  14. // 返回当前线程的唯一 ID,如有必要,请为其分配
  15. // Returns the current thread's unique ID, assigning it if necessary
  16. public static int get() {
  17. return threadId.get();
  18. }
  19. }

如果需要我们可以重写initialValue方法,实现自己的业务逻辑。
使用其实很简单,保存值就用set方法,最后调用remove方法删除数据,防止内存泄漏和数据混乱。

Threadlocal的数据结构

Thread类中有个变量threadLocals,这个类型为ThreadLocal中的一个内部类ThreadLocalMap,这个类没有实现map接口,就是一个普通的Java类,但是实现的类似map的功能。
ThreadLocal数据结构

ThreadLocal的源码分析

1、Thread类中有个变量threadLocals,类型为ThreadLocal.ThreadLocalMap,这个就是保存每个线程的私有数据。

  1. public class Thread implements Runnable {
  2. // 与此线程相关的线程本地值。此映射由 ThreadLocal 类维护。
  3. /* ThreadLocal values pertaining to this thread. This map is maintained
  4. * by the ThreadLocal class. */
  5. ThreadLocal.ThreadLocalMap threadLocals = null;
  6. }

2、ThreadLocalMap是ThreadLocal的内部类,每个数据用Entry保存,其中的Entry继承与WeakReference,用一个键值对存储,键为ThreadLocal的引用。为什么是WeakReference呢?如果是强引用,即使把ThreadLocal设置为null,GC也不会回收,因为ThreadLocalMap对它有强引用。

  1. /**
  2. * The entries in this hash map extend WeakReference, using
  3. * its main ref field as the key (which is always a
  4. * ThreadLocal object). Note that null keys (i.e. entry.get()
  5. * == null) mean that the key is no longer referenced, so the
  6. * entry can be expunged from table. Such entries are referred to
  7. * as "stale entries" in the code that follows.
  8. */
  9. static class Entry extends WeakReference<ThreadLocal<?>> {
  10. /** The value associated with this ThreadLocal. */
  11. Object value;
  12. Entry(ThreadLocal<?> k, Object v) {
  13. super(k);
  14. value = v;
  15. }
  16. }

3、ThreadLocal中的set方法的实现逻辑,先获取当前线程,取出当前线程的ThreadLocalMap,如果不存在就会创建一个ThreadLocalMap,如果存在就会把当前的ThreadLocal的引用作为键,传入的参数作为值存入map中。

  1. /**
  2. * Sets the current thread's copy of this thread-local variable
  3. * to the specified value. Most subclasses will have no need to
  4. * override this method, relying solely on the {@link #initialValue}
  5. * method to set the values of thread-locals.
  6. *
  7. * @param value the value to be stored in the current thread's copy of
  8. * this thread-local.
  9. */
  10. public void set(T value) {
  11. Thread t = Thread.currentThread();
  12. ThreadLocalMap map = getMap(t);
  13. if (map != null) {
  14. map.set(this, value);
  15. } else {
  16. createMap(t, value);
  17. }
  18. }

4、ThreadLocal中get方法的实现逻辑,获取当前线程,取出当前线程的ThreadLocalMap,用当前的ThreadLocal作为key在ThreadLocalMap查找,如果存在不为空的Entry,就返回Entry中的value,否则就会执行初始化并返回默认的值。

  1. /**
  2. * Returns the value in the current thread's copy of this
  3. * thread-local variable. If the variable has no value for the
  4. * current thread, it is first initialized to the value returned
  5. * by an invocation of the {@link #initialValue} method.
  6. *
  7. * @return the current thread's value of this thread-local
  8. */
  9. public T get() {
  10. Thread t = Thread.currentThread();
  11. ThreadLocalMap map = getMap(t);
  12. if (map != null) {
  13. ThreadLocalMap.Entry e = map.getEntry(this);
  14. if (e != null) {
  15. @SuppressWarnings("unchecked")
  16. T result = (T)e.value;
  17. return result;
  18. }
  19. }
  20. return setInitialValue();
  21. }
  22. /**
  23. * Returns {@code true} if there is a value in the current thread's copy of
  24. * this thread-local variable, even if that values is {@code null}.
  25. *
  26. * @return {@code true} if current thread has associated value in this
  27. * thread-local variable; {@code false} if not
  28. */
  29. boolean isPresent() {
  30. Thread t = Thread.currentThread();
  31. ThreadLocalMap map = getMap(t);
  32. return map != null && map.getEntry(this) != null;
  33. }
  34. /**
  35. * Variant of set() to establish initialValue. Used instead
  36. * of set() in case user has overridden the set() method.
  37. *
  38. * @return the initial value
  39. */
  40. private T setInitialValue() {
  41. T value = initialValue();
  42. Thread t = Thread.currentThread();
  43. ThreadLocalMap map = getMap(t);
  44. if (map != null) {
  45. map.set(this, value);
  46. } else {
  47. createMap(t, value);
  48. }
  49. if (this instanceof TerminatingThreadLocal) {
  50. TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
  51. }
  52. return value;
  53. }

5、ThreadLocal中remove方法的实现逻辑,还是先获取当前线程的ThreadLocalMap变量,如果存在就调用ThreadLocalMap的remove方法。ThreadLocalMap的存储就是数组的实行,因此需要确定的元素的位置,找到Entry,把Entry的键值对都设为null,最后Entry也设置为null,其实这其中会有哈希冲突,具体见下文。

  1. /**
  2. * Removes the current thread's value for this thread-local
  3. * variable. If this thread-local variable is subsequently
  4. * {@linkplain #get read} by the current thread, its value will be
  5. * reinitialized by invoking its {@link #initialValue} method,
  6. * unless its value is {@linkplain #set set} by the current thread
  7. * in the interim. This may result in multiple invocations of the
  8. * {@code initialValue} method in the current thread.
  9. *
  10. * @since 1.5
  11. */
  12. public void remove() {
  13. ThreadLocalMap m = getMap(Thread.currentThread());
  14. if (m != null) {
  15. m.remove(this);
  16. }
  17. }
  18. /**
  19. * Remove the entry for key.
  20. */
  21. private void remove(ThreadLocal<?> key) {
  22. Entry[] tab = table;
  23. int len = tab.length;
  24. int i = key.threadLocalHashCode & (len-1);
  25. for (Entry e = tab[i];
  26. e != null;
  27. e = tab[i = nextIndex(i, len)]) {
  28. if (e.get() == key) {
  29. e.clear();
  30. expungeStaleEntry(i);
  31. return;
  32. }
  33. }
  34. }

解决哈希冲突

ThreadLocal中的hash code非常简单,就是调用AtomicInteger的getAndAdd方法,参数是个固定值0x61c88647。
上面说过ThreadLocalMap的结构非常简单只用一个数组存储,并没有链表结构,当出现Hash冲突时采用线性查找的方式,所谓线性查找,就是根据初始key的hashcode值确定元素在table数组中的位置,如果发现这个位置上已经有其他key值得元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置。如果产生多次hash冲突,处理起来就没有HashMap的效率高,为了避免哈希冲突,使用尽量少的ThreadLocal变量。

最后

使用场景:一些ORM框架的session管理,web系统的会话管理等
简单总结:一个ThreadLocal只能保存一个变量的副本,如果需要多个,就得创建多个变量;
我们确定使用完需要执行remove避免内存泄漏。