欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ThreadLocal知识点解析

程序员文章站 2022-05-06 17:52:44
...

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源码深入分析