ThreadLocal

ThreadLocal的作用

    当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本

ThreadLocal常用方法

  1. public void set(Object value): 设置当前线程的线程局部变量的值。
  2. public Object get(): 该方法返回当前线程所对应的线程局部变量。
  3. public void remove(): 将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
  4. protected Object initialValue(): 返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。

TheadLocal的举例

public class ThreadId {
      // Atomic integer containing the next thread ID to be assigned
      private static final AtomicInteger nextId = new AtomicInteger(0);
 
      // Thread local variable containing each thread's ID
      private static final ThreadLocal<Integer> threadId =new ThreadLocal<Integer>() {
              @Override protected Integer initialValue() {
                  return nextId.getAndIncrement();
          }
      };
 
      // Returns the current thread's unique ID, assigning it if necessary
      public static int get() {
          return threadId.get();
      }
  }

ThreadLocal的源码分析

  ThreadLocal实现的核心是ThreadLocalMap这个类。ThreadLocalMap是ThreadLocal类的一个静态内部类,它实现了键值对的设置和获取(对比Map对象来理解),每个线程中都有一个独立的ThreadLocalMap副本,它所存储的值,只能被当前线程读取和修改。ThreadLocal类通过操作每一个线程特有的ThreadLocalMap副本,从而实现了变量访问在不同线程中的隔离。因为每个线程的变量都是自己特有的,完全不会有并发错误。还有一点就是,ThreadLocalMap存储的键值对中的键是this对象指向的ThreadLocal对象(其实不是这样的, 解释见"TheadLocal与WeakReference"), 而值就是你所设置的对象了。

ThreadLocal与WeakReference

  思考这样一个问题, ThreadLoca对象能不能被GC回收? 如果能, 是如何做到的?
  答案当然是能被GC回收, 前提是ThreadLocal对象不被其他变量引用(即不在任何一下GC Roots开始的引用树上).

  思考第二问题, ThreadLocal对象不是作为线程所持有的ThreadLocalMap存储的键值吗? 只要有线程存在, 不应该一直有变量指向ThreadLocal对象吗?
  这是一个很让人费解的问题, 网上很多文章都是这么, 包括我前面也是这么介绍的, 其实不然,查看源码发现, ThreadLocalMap中存放的是Entry类型的数组, 数组的下标是根据ThrealLocal的hashcode值通过位运算得到的(threadLocalHashCode & (len - 1), 其中len是Entry数组的长度), 而Entry其实是WeakReference类型. 所以在ThreadLocalMap中并没有ThreadLocal对象的强引用, 一旦外界没有其他变量强引用该ThreadLocal对象, 只剩下了它的弱引用, 根据若引用的特性, 在下次垃圾回收时, 即个回收该ThreadLocal对象.
        /**
         * The table, resized as necessary.
         * table.length MUST always be a power of two.
         */
        private Entry[] table;

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

  接着, 第三问题就来了. ThreadLocal对象被回收了, 而ThreadLocalMap中的Entry数组中还存放着该对象对应的value值呢? 怎么回收?
  在ThreadLocal的set和remove方法执行过程中就会触发清除被回收对象对应的value操作。 比如set方法调用时有可能触发ThreadLocalMap的resize()方法, 而该方法会遍历整个Entry数组, 一旦某个元素的key为null(及弱引用被回收), 会把该元素中的value强制设置为null.    此外,在ThreadLocalMap中的set方法和cleanSomeSlots方法,以及remove方法中也有相关额清除操作。 下面是ThreadLocalMap中resize方法的源码:
    private void resize() {
            Entry[] oldTab = table;
            int oldLen = oldTab.length;
            int newLen = oldLen * 2;
            Entry[] newTab = new Entry[newLen];
            int count = 0;

            for (int j = 0; j < oldLen; ++j) {
                Entry e = oldTab[j];
                if (e != null) {
                    ThreadLocal<?> k = e.get();
                    if (k == null) {
                        e.value = null; // Help the GC
                    } else {
                        int h = k.threadLocalHashCode & (newLen - 1);
                        while (newTab[h] != null)
                            h = nextIndex(h, newLen);
                        newTab[h] = e;
                        count++;
                    }
                }
            }

ThreadLocal与内存泄露

        ThreadLocal对象被回收, 但该对象对应的value不能自动回收。 出现这种情况的条件: ThreadLocal是本地变量, 使用完后, 不在调用其set和remove方法
        ThreadLocal不会被回收,value自然不会被自动回收,  比如定义成static类型,  这种情况value可能会被线程池中的同一个线程重复利用存放一下过期的的脏数据。

        在使用线程池的情况下,没有及时清理ThreadLocal,不仅是内存泄漏的问题,更严重的是可能导致业务逻辑出现问题。所以,使用ThreadLocal就跟加锁完要解锁一样,用完就调用remove方法清理数据。


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值