ThreadLocal 原理解析(并不能解决多线程共享数据安全问题)

Posted 高、远

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ThreadLocal 原理解析(并不能解决多线程共享数据安全问题)相关的知识,希望对你有一定的参考价值。

【1】一张类图了解下Thread、ThreadLocal、ThreadLocalMap、ThreadLocals

在这里插入图片描述
①ThreadLocal是一个类。ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量。
②ThreadLocalMap是ThreadLocal的一个静态内部类,类似hashMap的存储结构 key-value 的形式
③Thread是线程类,任何一个执行线程都是这个类的子类
④ThreadLocals 是Thread类中的一个属性变量,类型是ThreadLocalMap


【2】 ThreadLocal简介

①Java的ThreadLocal不是设计用来解决多线程安全问题的,事实证明也解决不了,共享变量a还是会被随意更改。ThreadLocal无能为力。所以,一般用ThreadLocal都不会将一个共享变量放到线程的ThreadLocal中。一般来讲,存放到ThreadLocal中的变量都是当前线程本身就独一无二的一个变量。其他线程本身就不能访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量。

②ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一乐ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,
  如下图所示

【3】ThreadLocal简单使用

下面的例子中,开启两个线程,在每个线程内部设置了本地变量的值,然后调用print方法打印当前本地变量的值。如果在打印之后调用本地变量的remove方法会删除本地内存中的变量,
  代码如下所示

 1 package test;
 2 
 3 public class ThreadLocalTest {
 4 
 5     static ThreadLocal<String> localVar = new ThreadLocal<>();
 6 
 7     static void print(String str) {
 8         //打印当前线程中本地内存中本地变量的值
 9         System.out.println(str + " :" + localVar.get());
10         //清除本地内存中的本地变量
11         localVar.remove();
12     }
13 
14     public static void main(String[] args) {
15         Thread t1  = new Thread(new Runnable() {
16             @Override
17             public void run() {
18                 //设置线程1中本地变量的值
19                 localVar.set("localVar1");
20                 //调用打印方法
21                 print("thread1");
22                 //打印本地变量
23                 System.out.println("after remove : " + localVar.get());
24             }
25         });
26 
27         Thread t2  = new Thread(new Runnable() {
28             @Override
29             public void run() {
30                 //设置线程1中本地变量的值
31                 localVar.set("localVar2");
32                 //调用打印方法
33                 print("thread2");
34                 //打印本地变量
35                 System.out.println("after remove : " + localVar.get());
36             }
37         });
38 
39         t1.start();
40         t2.start();
41     }
42 }

下面是运行后的结果:

在这里插入图片描述

【4】ThreadLocal的实现原理

下面是ThreadLocal的类图结构,从图中可知:Thread类中有两个变量·threadLocals·和·inheritableThreadLocals·,二者都是ThreadLocal内部类ThreadLocalMap类型的变量,我们通过查看内部内ThreadLocalMap可以发现实际上它类似于一个HashMap。在默认情况下,每个线程中的这两个变量都为null,只有当线程第一次调用ThreadLocal的set或者get方法的时候才会创建他们(后面我们会查看这两个方法的源码)。除此之外,和我所想的不同的是,每个线程的本地变量不是存放在ThreadLocal实例中,而是放在调用线程的ThreadLocals变量里面(前面也说过,该变量是Thread类的变量;这也是ThreadLocal的精髓)。也就是说,ThreadLocal类型的本地变量是存放在具体的线程空间上,其本身相当于一个装载本地变量的工具壳,通过set方法将value添加到调用线程的threadLocals中,当调用线程调用get方法时候能够从它的threadLocals中取出变量。如果调用线程一直不终止,那么这个本地变量将会一直存放在他的threadLocals中,所以不使用本地变量的时候需要调用remove方法将threadLocals中删除不用的本地变量。
  下面我们通过查看ThreadLocal的set、get以及remove方法来查看ThreadLocal具体实怎样工作的

1、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为当前定义的ThreadLocal变量的this引用,值为添加的本地变量值
 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中。

2、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 }

3、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  }

4、如下图所示:每个线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap),其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值。每个线程的本地变量存放在自己的本地内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量就会一直存在(所以可能会导致内存溢出),因此使用完毕需要将其remove掉。
在这里插入图片描述

【5】ThreadLocal被设计出来并不能解决多线程共享数据安全问题

以前看到ThreadLocal可以解决线程安全问题的时候,总是很困惑他到对是怎么解决的?是如何操作本线程独有的ThreadLocals中的 key-value,使她与其他线程的ThreadLocals中的值统一的。知道最近看到了一些大佬的文章,恍然大悟,原来ThreadLocal被设计出来本来就不是用来解决线程安全问题的。

Java的ThreadLocal不是设计用来解决多线程安全问题的,事实证明也解决不了,共享变量a还是会被随意更改。ThreadLocal无能为力。所以,一般用ThreadLocal都不会将一个共享变量放到线程的ThreadLocal中。一般来讲,存放到ThreadLocal中的变量都是当前线程
本身就独一无二的一个变量。其他线程本身就不能访问,存到ThreadLocal中只是为了方便在程序中同一个线程之间传递这个变量。

这里我不详谈,直接给出链接:
ThreadLocal能解决线程安全问题?胡扯!本文教你正确的使用姿势【享学Java】

谈谈ThreadLocal和解决线程安全的关系

以上是关于ThreadLocal 原理解析(并不能解决多线程共享数据安全问题)的主要内容,如果未能解决你的问题,请参考以下文章

ThreadLocal 应用原理解析与常见问题

多线程之ThreadLocal

ThreadLocal应用和原理解析

Java 线程系列ThreadLocal进阶解析

ThreadLocal的原理及用法

ThreadLocal和异步