ThreadLoacal 理解以及源码分析

ThreadLoacal 理解以及源码分析

问题

  1. 什么是ThreadLocal
  2. ThreadLocal的目的和作用
  3. ThreadLocal原理?
  4. ThreadLocal使用实例
  5. ThreadLocal使用场景

ThreadLocal介绍

线程内局部变量。用于实现线程内数据共享,即对于一个相同的代码模块,每个线程访问的时候代码模块内的变量互补干扰,互不影响。

ThreadLocal目的

ThreadLocal的主要目的是为了解决变量在单个线程内部变量的传递问题,ThreadLocal修饰的变量多个线程之间不共享,所以不存在安全性问题,因为数据都不共享,所以ThreadLocal不是为了解决多线程之间的安全问题。

ThreadLocal作用

ThreadLocal提供了线程本地变量,它可以保证访问到的变量属于当前线程,每个线程都保存有一个变量副本,每个线程的变量都不同,而同一个线程在任何时候访问这个本地变量的结果都是一致的。当此线程结束生命周期时,所有的线程本地实例都会被GC。ThreadLocal相当于提供了一种线程隔离,将变量与线程相绑定。 Thread Local的作用于是线程内部,伴随线程执行始终,线程结束,变量生命结束。

ThreadLocal源码分析

ThreadLocal 定义了4个方法:

  • get():返回此线程局部变量的当前线程副本中的值。
  • initialValue():返回此线程局部变量的当前线程的“初始值”。
  • remove():移除此线程局部变量当前线程的值。
  • set(T value):将此线程局部变量的当前线程副本中的值设置为指定值。

ThreadLocal的核心是通过静态内部类ThreadLocalMap来处理数据的,我们先看下ThreadLocalMap的源码。

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

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

Entry是ThreadLocalMap核心存储key-value的。,继承WeakReference,Entry所对应key(ThreadLocal实例)的引用为一个弱引用 。

看一下set方法:

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为空,但是value是有值的
        if (k == null) {
            //用新元素替代老旧的元素的值
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    //如果key不重复,并且也不为空,则实例化一个Entry
    tab[i] = new Entry(key, value);
    int sz = ++size;
    //清除陈旧的Entry,如果数组的长度大于阈值,及 len * 2 / 3,则进行rehash();
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

有心的朋友可能会发现,这边的是采用线性探测法 ,而HashMap采用的是拉链法。

set()除了存储数据之外,还清楚了key为空的数据,以及清除没有使用的实体。

再看下getEntry()

private Entry getEntry(ThreadLocal<?> key) {
     //根据散列计算出元素在数组中的位置。
    int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    //key存在则直接返回Entry。
    if (e != null && e.get() == key)
        return e;
    //因为是使用开放定址法,所以当前的key的散列值和元素的索引并不是完全对应的
    else
        return getEntryAfterMiss(key, i, e);
}
 private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
     Entry[] tab = table;
     int len = tab.length;

     while (e != null) {
         ThreadLocal<?> k = e.get();
         if (k == key)
             return e;
         //当key为空的时候,处理key为空的Entry,有利于GC的回收,有效避免内存泄漏
         if (k == null)
             expungeStaleEntry(i);
         else
             i = nextIndex(i, len);
         e = tab[i];
     }
     return null;
 }

好了,现在再回头看ThreadLocal的几个方法

get()

返回当前线程对应的线程变量的值

   public T get() {
       //获取当前线程
        Thread t = Thread.currentThread();
       //通过当前线程获取当前的ThreadLocalMap对象
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            //通过key获取实体Entry
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
       //如果map为空,则创建一个新的Map ,并返回空值
        return setInitialValue();
    }
set(T value)

设置当前线程的局部变量值

 public void set(T value) {
     //获取当前线程
     Thread t = Thread.currentThread();
     //通过当前线程获取当前的ThreadLocalMap对象
     ThreadLocalMap map = getMap(t);
     //如果map存在则直接设置value,不存在则创建map并设置value。
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
 }
remove()

将当前线程局部变量的值删除。

 public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null)
         m.remove(this);
 }

此方法目的是减少内存的占用,不需要显示调用此方法,线程结束后,对应的局部变量会自动回收。

initialValue()

返回当前线程的局部变量的初始。

protected T initialValue() {
    return null;
}

此方法是protected的,一般是子类实现,赋值初始值,无法被显式调用。

ThreadLocal使用场景

  • 数据库链接
  • session管理

总结

对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,比如定义一个static变量,同步访问,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 ThreadLocal并不是为了解决多线程的数据共享,只是从另外一个方向来解决并发的问题,让变量线程局部化,就不存在并发。

猜你喜欢

转载自blog.csdn.net/wagnteng/article/details/80706945