ThreadLocal 原理及其扩展

一、 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 变量。

总结:InheritableThreadLocal 类通过重写了 getMapcreateMap 让本地变量保存到了具体线程的 inheritableThreadLocals 变量里面,那么线程在通过 InheritableThreadLocal 类实例的 setget 方法设置变量时,就会创建当前线程的 inheritableThreadLocals 变量。当父线程创建子线程时,构造函数会把父线程中 inheritableThreadLocals 变量里面的本地变量复制一份保存到子线程的 inheritableThreadLocals 变量里面。

2. InheritableThreadLocal 的使用场景

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

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值