ThreadLocal知识点解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/programmer_at/article/details/79432212

1. 作用

  • (具体作用)为每个线程都维护一个* 线程局部变量* ;
  • (目的)每个线程都可以修改自己内部的变量本,而不影响其他线程的变量
  • (重要区分:optional)属于线程安全范畴,但是不是为了解决线程同步问题:多个线程对同一个资源的并发访问。

2. 每个线程的局部变量是存储在哪里的?

类似问题:

ThreadLocal是如何每个线程维护局部变量的?

==每个线程都保留了一个对ThreadLocalMap的引用(threadlocals),由该ThreadLocalMap存储该线程的变量副本,而该ThreadLocalMap每个entry中的key为ThrealLocal(通过threadLocalHashCode保证其唯一性),value为变量副本。==

详细解析如下:

Thread类中有个缺省类型的成员变量ThreadLocalMap threadLocals = null;

在每个线程中,都维护了一个threadlocals对象,在没有ThreadLocal变量的时候是null的。一旦在ThreadLocal的createMap函数中初始化之后,这个threadlocals就初始化了。

//Threadlocal.class
void createMap(Thread var1, T var2) {
        var1.threadLocals = new ThreadLocal.ThreadLocalMap(this, var2);
    }
  • 什么时候调用createMap()?
    调用ThreadLocal的get函数或set函数时,而threadlocals为null时,那么ThreadLocal会去调用createMap()

以后每次那个ThreadLocal对象想要访问变量的时候,比如set函数和get函数,都是先通过getMap(当前线程)函数,先将线程的map取出,然后再从这个map中取出数据【以当前threadlocal作为参数】。

//Threadlocal.class  

//get()调用过程
public T get() {
        Thread var1 = Thread.currentThread();
        ThreadLocal.ThreadLocalMap var2 = this.getMap(var1);
        if(var2 != null) {  // ThreadLocalMap已经被创建
            ThreadLocal.ThreadLocalMap.Entry var3 = var2.getEntry(this);
            if(var3 != null) {
                Object var4 = var3.value;
                return var4;
            }
        }

        return this.setInitialValue();  // ThreadLocalMap为空或者ThreadLocal不在ThreadLocalMap的键中
    }

private T setInitialValue() {
        Object var1 = this.initialValue();  // var1 = null;
        Thread var2 = Thread.currentThread();
        ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        if(var3 != null) {
            // ThreadLocal不在ThreadLocalMap中, 新建Entry,key为该ThreadLocal,value为空
            var3.set(this, var1);
        } else {
            // ThreadLocalMap为空
            this.createMap(var2, var1);
        }

        return var1;
    }


//set()调用过程
public void set(T var1) {
        Thread var2 = Thread.currentThread();
        ThreadLocal.ThreadLocalMap var3 = this.getMap(var2);
        if(var3 != null) {
            var3.set(this, var1);
        } else {
            this.createMap(var2, var1);
        }

    }

有趣的是,Thread本身除了在线程退出时(exit())重新将threadlocal置null,其他时候对threadlocal的访问均由ThreadLocal的getMap()提供。

// Threadlocal.class
ThreadLocal.ThreadLocalMap getMap(Thread var1) {
        return var1.threadLocals;
    }

3. 每个线程的局部变量是怎么存储的?

局部变量存储在ThreadLocalMap中,该Map其实是一个Entry类的数组,每个Entry均以该局部变量的ThreadLocal作为key,具体的数据为value的key-value对。当ThreadLocal发生冲突时,将采用“线性探测法”来解决冲突。

ThreadLocalMap详解

ThreadLocalMap是定义在ThreadLocal静态内部类,其中
ThreadLocal.ThreadLocalMap.Entry[] table就是用于存储线程局部变量。

Entry类是ThreadLocalMap的静态内部类,用于存储数据。它的源码如下:

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

Entry类继承了WeakReference

// ThreadLocal.ThreadLocalMap.class
/**
 * Set the value associated with key.
 *
 * @param key the thread local object
 * @param value the value to be 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;
    // 这相当于取模运算hashCode % size的一个更高效的实现.
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();
        if (k == key) {
            e.value = value;
            return;
        }
        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }
    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}


/**
 * Increment i modulo len.
 */
private static int nextIndex(int i, int len) {
    return ((i + 1 < len) ? i + 1 : 0);
}

i = (n - 1) & hash来计算当前Entry存放的位置(index),而老版本的JDK使用i = hash % n,只有当n为2的整数次幂的时候,这两个计算((n - 1) & hash和hash%n)才能等价。使用按位与&取代取模%主要是因为&一般是单周期指令而%需要用到除法器,速度相差好几倍。

4. 为什么ThreadLocalMap的键是ThreadLocal而不是Thread呢?

假如我们把ThreadLocalMap做成一个Map<t extends Thread, ?>类型的Map,那么它存储的东西将会非常多(相当于一张全局线程本地变量表),这样的情况下用线性探测法解决哈希冲突的问题效率会非常差。

5. 典型的应用场景

Spring对一些Bean中的成员变量采用ThreadLocal进行处理,让它们可以成为线程安全的。举个例子:

package org.springframework.web.context.request;
public abstract class RequestContextHolder  {
    private static final boolean jsfPresent =
            ClassUtils.isPresent("javax.faces.context.FacesContext", RequestContextHolder.class.getClassLoader());
    private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
            new NamedThreadLocal<RequestAttributes>("Request attributes");
    private static final ThreadLocal<RequestAttributes> 16M5mvqRwbyuTpLduM9yWFa5PWocJXnEUN =
            new NamedInheritableThreadLocal<RequestAttributes>("Request context");
            //......下面省略
        }

几个十分值得入门的链接:
Java ThreadLocal的使用
并发编程 | ThreadLocal源码深入分析

猜你喜欢

转载自blog.csdn.net/programmer_at/article/details/79432212
今日推荐