0
点赞
收藏
分享

微信扫一扫

ThreadLocal 原理及其扩展

sin信仰 2022-03-30 阅读 80

一、 ThreadLocal

概述

ThreadLocal 是JDK 提供的,它提供了单个线程访问的本地变量。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个 ThreadLocal 变量后,每个线程都会复制一个变量到自己的本地内存。

使用示例

如下示例中,开启了两个线程,都设置了 ThreadLocal 的本地变量值,然后调用 print() 打印当前本地变量。如果打印后调用了本地变量的 remove 方法,则删除本地内存中的该变量。

public class ThreadLocalTest {

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

    static void print() {
        System.out.println(Thread.currentThread().getName() + " 获取的值为:" + localVariables.get());
        localVariables.remove();
    }

    public static void main(String[] args) {
        Thread first_thread = new Thread(new Runnable() {
            public void run() {
                localVariables.set("first thread's local variable");
                print();
                System.out.println("first thread remove after:" + localVariables.get());
            }
        });
        first_thread.setName("first thread ");

        Thread second_thread = new Thread(new Runnable() {
            public void run() {
                localVariables.set("second thread's local variable");
                print();
                System.out.println("second thread remove after:" + localVariables.get());
            }
        });
        second_thread.setName("second thread ");

        first_thread.start();
        second_thread.start();
    }
}

// 控制台输出
first thread  获取的值为:first thread's local variable
second thread  获取的值为:second thread's local variable
first thread remove after:null
second thread remove after:null

从上面的示例可以看出,在同一个 localVariables 中设置的值,但可以通过线程区分开,互不影响的访问。那么就看看 ThreadLocal 的原理是如何实现的。

二、 ThreadLocal 实现原理

1. java.lang.ThreadLocal#set

public void set(T value) {
	// 获取当前线程
    Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t); //(1) 获取map 
     if (map != null)
         map.set(this, value); 
     else
         createMap(t, value); //(2)创建Map
}

1)获取 map :java.lang.ThreadLocal#getMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; // Thread 类中的属性:  ThreadLocal.ThreadLocalMap threadLocals
}

查看 ThreadLocal.ThreadLocalMap,类图如下(就暂且认为是一个 Map ):
在这里插入图片描述

2) 创建 map: java.lang.ThreadLocal#createMap

 void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
 }

它创建当前线程的 threadLocals 变量。

2. java.lang.ThreadLocal#get

 public T get() {
 // 获取当前线程
    Thread t = Thread.currentThread();
    //获取当前线程的 threadLocals 变量
     ThreadLocalMap map = getMap(t);//(1)
     if (map != null) {
         ThreadLocalMap.Entry e = map.getEntry(this);
         if (e != null) {
             T result = (T)e.value;
             return result;
         }
     }
     //如果 threadLocals 为空,则初始化当前线程的 threadLocals 成员变量
     return setInitialValue();
 }

1) java.lang.ThreadLocal#getMap

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

2) java.lang.ThreadLocal#setInitialValue

private T setInitialValue() {
    T value = initialValue();// 初始化值,实际返回null
    Thread t = Thread.currentThread();
     ThreadLocalMap map = getMap(t);
     if (map != null)
         map.set(this, value);
     else
         createMap(t, value);
     return value;
}

//java.lang.ThreadLocal#initialValue
 protected T initialValue() {
    return null;
 }

3. java.lang.ThreadLocal#remove

public void remove() {
// 如果当前线程的 threadLocals 变量不为空,则删除当前线程中指定 ThreadLocal 实例的本地变量
  ThreadLocalMap m = getMap(Thread.currentThread());
    if (m != null)
        m.remove(this);
}

4. ThreadLocal 总结

在每个线程内部都有一个名称为 threadLocals 的成员变量,该变量的类型为 HashMap, 其中 key 为我们定义的 ThreadLocal 变量的 this 引用,value 则为我们使用 set 方法设置的值。每个线程的本地变量存放在线程自己的内存变量 threadLocals 中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用 ThreadLocalremove 方法删除对应线程的 threadLocals 中的本地变量。

5、 ThreadLocal 不支持继承性

public class ThreadLocalTestShare {

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

    public static void main(String[] args) throws InterruptedException {
        threadLocal.set("hello world");
        Thread thread = new Thread(new Runnable() {
            public void run() {
                // 获取主线程设置的值
                System.out.println("sub thread : "+threadLocal.get());
            }
        });
        thread.start();
        System.out.println("main: "+threadLocal.get());
    }
}
// 控制台输出:
sub thread : null
main: hello world

如上代码所示,同一个 ThreadLocal 变量在父线程中设置值后,在子线程中获取不到。因为在子线程 thread 里面调用 get 方法时当前线程为 thread,而调用 set 方法的线程是 main 线程,两者是不同的线程,自然子线程访问时返回 null。那么有没有办法让子线程访问到父线程中的值?使用 InheritableThreadLocal 类就可以实现。

三、InheritableThreadLocal 类

public class InheritableThreadLocal<T> extends ThreadLocal<T> {
  //(1)
    protected T childValue(T parentValue) {
        return parentValue;
    }
    
  //(2)
    ThreadLocalMap getMap(Thread t) {
       return t.inheritableThreadLocals;
    }
    
   //(3)
    void createMap(Thread t, T firstValue) {
        t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
    }
}

1. InheritableThreadLocal 实现的原理

如上代码可知,InheritableThreadLocal 继承 ThreadLocal,并重写了3个方法。选中 childValue 查看引用的地方:

//java.lang.ThreadLocal.ThreadLocalMap#ThreadLocalMap(java.lang.ThreadLocal.ThreadLocalMap)

 private ThreadLocalMap(ThreadLocalMap parentMap) {
   Entry[] parentTable = parentMap.table;
    int len = parentTable.length;
    setThreshold(len);
    table = new Entry[len];

    for (int j = 0; j < len; j++) {
        Entry e = parentTable[j];
        if (e != null) {
            @SuppressWarnings("unchecked")
            ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
            if (key != null) {
            // 在这里调用了 childValue()
                Object value = key.childValue(e.value);
                Entry c = new Entry(key, value);
                int h = key.threadLocalHashCode & (len - 1);
                while (table[h] != null)
                    h = nextIndex(h, len);
                table[h] = c;
                size++;
            }
        }
    }
}

//继续查看 ThreadLocalMap() 构造使用过的地方:
//java.lang.ThreadLocal#createInheritedMap

 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
    return new ThreadLocalMap(parentMap);
  }

// 继续查看 createInheritedMap 使用过的地方:
//java.lang.Thread#init(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String, long, java.security.AccessControlContext, boolean)

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;
		// 获取当前线程:
        Thread parent = currentThread();
       //....
	   // 如果父线程的 inheritableThreadLocals  变量不为 null 
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
 ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);// 这里调用 createInheritedMap
        this.stackSize = stackSize;
        tid = nextThreadID();
}

回顾整个调用的过程,线程在创建时,在构造函数里面会调用 init 方法,然后会判断 main 函数所在的线程里的 inheritableThreadLocals 属性是否为 null,又由于在 java.lang.InheritableThreadLocal#getMap(Thread t) 和 java.lang.InheritableThreadLocal#createMap(Thread t, T firstValue) 中都有在使用 inheritableThreadLocals 属性,因此这里 inheritableThreadLocals 不为 null,在 createInheritedMap 内部使用 parent 的 inheritableThreadLocals 变量作为构造函数创建了一个新的 ThreadLocalMap 变量,然后赋给了子线程的 inheritableThreadLocals 变量。

2. InheritableThreadLocal 的使用场景

1)子线程需要使用存放在 threadLocal 变量中的用户登录信息

2) 比如一些中间件需要把统一的 id 追踪的整个调用链路记录下来。

举报

相关推荐

0 条评论