java并发6-ThreadLocal实现分析

一、ThreadLocal的实现原理

1、ThreadLocal特性

一个线程可以有多个ThreadLocal对象(一般用一个),多个ThreadLocal对应同一个ThreadLocalMap。
ThreadLocalMap本质是一个entry的数组。下面的A和B对应同一个map的不同entry数组下标。
ThreadLocal sThreadLocalA = new ThreadLocal();
ThreadLocal sThreadLocalB = new ThreadLocal();

ThreadLocal和Synchronized都是为了解决多线程中相同变量的访问冲突问题,不同的点是
Synchronized是通过线程等待,牺牲时间来解决访问冲突
ThreadLocal是通过每个线程单独一份存储空间,牺牲空间来解决冲突,并且相比于Synchronized,ThreadLocal具有线程隔离的效果,只有在线程内才能获取到对应的值,线程外则不能访问到想要的值。

在这里插入图片描述

img

2、set方法源码

 1 public void set(T value) {
 2     //(1)获取当前线程(调用者线程)
 3     Thread t = Thread.currentThread();
 4     //(2)以当前线程作为key值,去查找对应的线程变量,找到对应的map
 5     ThreadLocalMap map = getMap(t);
 6     //(3)如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
 7     if (map != null)
 8         map.set(this, value);
 9     //(4)如果map为null,说明首次添加,需要首先创建出对应的map
10     else
11         createMap(t, value);
12 }

在上面的代码中,(2)处调用getMap方法获得当前线程对应的threadLocals(参照上面的图示和文字说明),该方法代码如下

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals; //获取线程自己的变量threadLocals,并绑定到当前调用线程的成员变量threadLocals上
}

如果调用getMap方法返回值不为null,就直接将value值设置到threadLocals中(key为当前线程引用,值为本地变量);如果getMap方法返回null说明是第一次调用set方法(前面说到过,threadLocals默认值为null,只有调用set方法的时候才会创建map),这个时候就需要调用createMap方法创建threadLocals,该方法如下所示

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

createMap方法不仅创建了threadLocals,同时也将要添加的本地变量值添加到了threadLocals中。

3、get方法源码

在get方法的实现中,首先获取当前调用者线程,如果当前线程的threadLocals不为null,就直接返回当前线程绑定的本地变量值,否则执行setInitialValue方法初始化threadLocals变量。在setInitialValue方法中,类似于set方法的实现,都是判断当前线程的threadLocals变量是否为null,是则添加本地变量(这个时候由于是初始化,所以添加的值为null),否则创建threadLocals变量,同样添加的值为null。

 1 public T get() {
 2     //(1)获取当前线程
 3     Thread t = Thread.currentThread();
 4     //(2)获取当前线程的threadLocals变量
 5     ThreadLocalMap map = getMap(t);
 6     //(3)如果threadLocals变量不为null,就可以在map中查找到本地变量的值
 7     if (map != null) {
 8         ThreadLocalMap.Entry e = map.getEntry(this);
 9         if (e != null) {
10             @SuppressWarnings("unchecked")
11             T result = (T)e.value;
12             return result;
13         }
14     }
15     //(4)执行到此处,threadLocals为null,调用该更改初始化当前线程的threadLocals变量
16     return setInitialValue();
17 }
18 
19 private T setInitialValue() {
20     //protected T initialValue() {return null;}
21     T value = initialValue();
22     //获取当前线程
23     Thread t = Thread.currentThread();
24     //以当前线程作为key值,去查找对应的线程变量,找到对应的map
25     ThreadLocalMap map = getMap(t);
26     //如果map不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
27     if (map != null)
28         map.set(this, value);
29     //如果map为null,说明首次添加,需要首先创建出对应的map
30     else
31         createMap(t, value);
32     return value;
33 }

4、remove方法的实现

remove方法判断该当前线程对应的threadLocals变量是否为null,不为null就直接删除当前线程中指定的threadLocals变量

1  public void remove() {
2     //获取当前线程绑定的threadLocals
3      ThreadLocalMap m = getMap(Thread.currentThread());
4      //如果map不为null,就移除当前线程中指定ThreadLocal实例的本地变量
5      if (m != null)
6          m.remove(this);
7  }

5、ThreadLocalMap

实例化ThreadLocalMap时创建了一个长度为16的Entry数组

二、ThreadLocal不支持继承性

同一个ThreadLocal变量在父线程中被设置值后,在子线程中是获取不到的。(threadLocals中为当前调用线程对应的本地变量,所以二者自然是不能共享的)

 1 package test;
 2 
 3 public class ThreadLocalTest2 {
 4 
 5     //(1)创建ThreadLocal变量
 6     public static ThreadLocal<String> threadLocal = new ThreadLocal<>();
 7 
 8     public static void main(String[] args) {
 9         //在main线程中添加main线程的本地变量
10         threadLocal.set("mainVal");
11         //新创建一个子线程
12         Thread thread = new Thread(new Runnable() {
13             @Override
14             public void run() {
15                 System.out.println("子线程中的本地变量值:"+threadLocal.get());
16             }
17         });
18         thread.start();
19         //输出main线程中的本地变量值
20         System.out.println("mainx线程中的本地变量值:"+threadLocal.get());
21     }
22 }

三、InheritableThreadLocal类

在上面说到的ThreadLocal类是不能提供子线程访问父线程的本地变量的,而InheritableThreadLocal类则可以做到这个功能

 1 public class InheritableThreadLocal<T> extends ThreadLocal<T> {
 2     
 3     protected T childValue(T parentValue) {
 4         return parentValue;
 5     }
 6 
 7     ThreadLocalMap getMap(Thread t) {
 8        return t.inheritableThreadLocals;
 9     }
10 
11     void createMap(Thread t, T firstValue) {
12         t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
13     }
14 }

从上面代码可以看出,InheritableThreadLocal类继承了ThreadLocal类,并重写了childValue、getMap、createMap三个方法。其中createMap方法在被调用(当前线程调用set方法时得到的map为null的时候需要调用该方法)的时候,创建的是inheritableThreadLocal而不是threadLocals。同理,getMap方法在当前调用者线程调用get方法的时候返回的也不是threadLocals而是inheritableThreadLocal。

下面我们看看重写的childValue方法在什么时候执行,怎样让子线程访问父线程的本地变量值。我们首先从Thread类开始说起

 1 private void init(ThreadGroup g, Runnable target, String name,
 2                   long stackSize) {
 3     init(g, target, name, stackSize, null, true);
 4 }
 5 private void init(ThreadGroup g, Runnable target, String name,
 6                   long stackSize, AccessControlContext acc,
 7                   boolean inheritThreadLocals) {
 8     //判断名字的合法性
 9     if (name == null) {
10         throw new NullPointerException("name cannot be null");
11     }
12 
13     this.name = name;
14     //(1)获取当前线程(父线程)
15     Thread parent = currentThread();
16     //安全校验
17     SecurityManager security = System.getSecurityManager();
18     if (g == null) { //g:当前线程组
19         if (security != null) {
20             g = security.getThreadGroup();
21         }
22         if (g == null) {
23             g = parent.getThreadGroup();
24         }
25     }
26     g.checkAccess();
27     if (security != null) {
28         if (isCCLOverridden(getClass())) {
29             security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
30         }
31     }
32 
33     g.addUnstarted();
34 
35     this.group = g; //设置为当前线程组
36     this.daemon = parent.isDaemon();//守护线程与否(同父线程)
37     this.priority = parent.getPriority();//优先级同父线程
38     if (security == null || isCCLOverridden(parent.getClass()))
39         this.contextClassLoader = parent.getContextClassLoader();
40     else
41         this.contextClassLoader = parent.contextClassLoader;
42     this.inheritedAccessControlContext =
43             acc != null ? acc : AccessController.getContext();
44     this.target = target;
45     setPriority(priority);
46     //(2)如果父线程的inheritableThreadLocal不为null
47     if (inheritThreadLocals && parent.inheritableThreadLocals != null)
48         //(3)设置子线程中的inheritableThreadLocals为父线程的inheritableThreadLocals
49         this.inheritableThreadLocals =
50             ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
51     this.stackSize = stackSize;
52 
53     tid = nextThreadID();
54 }

在init方法中,首先(1)处获取了当前线程(父线程),然后(2)处判断当前父线程的inheritableThreadLocals是否为null,然后调用createInheritedMap将父线程的inheritableThreadLocals作为构造函数参数创建了一个新的ThreadLocalMap变量,然后赋值给子线程。下面是createInheritedMap方法和ThreadLocalMap的构造方法

 1 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
 2     return new ThreadLocalMap(parentMap);
 3 }
 4 
 5 private ThreadLocalMap(ThreadLocalMap parentMap) {
 6     Entry[] parentTable = parentMap.table;
 7     int len = parentTable.length;
 8     setThreshold(len);
 9     table = new Entry[len];
10 
11     for (int j = 0; j < len; j++) {
12         Entry e = parentTable[j];
13         if (e != null) {
14             @SuppressWarnings("unchecked")
15             ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
16             if (key != null) {
17                 //调用重写的方法
18                 Object value = key.childValue(e.value);
19                 Entry c = new Entry(key, value);
20                 int h = key.threadLocalHashCode & (len - 1);
21                 while (table[h] != null)
22                     h = nextIndex(h, len);
23                 table[h] = c;
24                 size++;
25             }
26         }
27     }
28 }

在构造函数中将父线程的inheritableThreadLocals成员变量的值赋值到新的ThreadLocalMap对象中。返回之后赋值给子线程的inheritableThreadLocals。总之,InheritableThreadLocals类通过重写getMap和createMap两个方法将本地变量保存到了具体线程的inheritableThreadLocals变量中,当线程通过InheritableThreadLocals实例的set或者get方法设置变量的时候,就会创建当前线程的inheritableThreadLocals变量。而父线程创建子线程的时候,ThreadLocalMap中的构造函数会将父线程的inheritableThreadLocals中的变量复制一份到子线程的inheritableThreadLocals变量中。

四、ThreadLocal使用不当的内存泄漏问题

每一个Thread维护一个ThreadLocalMap映射表,映射表的key是ThreadLocal实例,并且使用的是ThreadLocal的弱引用 ,value是具体需要存储的Object。下面用一张图展示这些对象之间的引用关系,实心箭头表示强引用,空心箭头表示弱引用。

在这里插入图片描述

从上图可以看出,如果ThreadLocal没有外部强引用,当发生垃圾回收时,这个ThreadLocal一定会被回收(弱引用的特点是不管当前内存空间足够与否,GC时都会被回收),这样就会导致ThreadLocalMap中出现key为null的Entry,外部将不能获取这些key为null的Entry的value,并且如果当前线程一直存活,那么就会存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,导致value对应的Object一直无法被回收,产生内存泄露。查看源码会发现,ThreadLocal的get、set和remove方法都实现了对所有key为null的value的清除,但仍可能会发生内存泄露,因为可能使用了ThreadLocal的get或set方法后发生GC,此后不调用get、set或remove方法,为null的value就不会被清除。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值