ThreadLocal 使用及原理

        多线程并发操作一个共享变量时会造成线程安全问题,使用ThreadLocal可以保证共享变量线程安全。由于同一个 ThreadLocal 所包含的对象,在不同的 Thread 中有不同的副本,当线程访问ThreadLocal时,每个线程都操作自己的独立副本,避免造成线程安全问题。

简单使用

        每个线程分别对ThreadLocal赋值、打印。

  static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

  public static void main(String[] args) {

    new Thread("thread-1") {
      @Override
      public void run() {
        operateThreadLocal("thread-1");
      }
    }.start();
    new Thread("thread-2") {
      @Override
      public void run() {
        operateThreadLocal("thread-2");
      }
    }.start();
  }

  /**
   * 操作ThreadLocal
   * 
   * @param value
   */
  public static void operateThreadLocal(String value) {
    threadLocal.set(value);
    System.out.println(threadLocal.get());
    threadLocal.remove(); // 使用完对其进行remove,防止内存泄露
  }

源码分析

        ThreadLocal源码比较简单,内部基于ThreadLocalMap对每个线程设置的值进行保存,其中key为线程,value为设置的值。获取时根据当前线程获取。

set方法:

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


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

            // We don't use a fast path as with get() because it is at
            // least as common to use set() to create new entries as
            // it is to replace existing ones, in which case, a fast
            // path would fail more often than not.

            Entry[] tab = table;
            int len = tab.length;
            // 计算放入数据中位置
            int i = key.threadLocalHashCode & (len-1);

            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                ThreadLocal<?> k = e.get();
                // key值相等进行替换
                if (k == key) {
                    e.value = value;
                    return;
                }
                // 该位置原key为null,则替换为新key,value
                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }
            // 创建entry放入数组中,key为WeakReference弱引用,下次gc会被回收
            tab[i] = new Entry(key, value);
            int sz = ++size;
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }

get方法:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        // map不为null则查找set放入的entry
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

remove方法:由于set时entry的key为弱引用,发生gc会被回收,根据可达性分析法,其value永远不会被回收,无法释放占用的内存空间造成内存泄露。所以用完之后通过remove方法清理entry可防止内存泄露。

 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    // 清除对象引用
                    e.clear();
                    // 清除value,修改数组大小
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值