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 原理解析(并不能解决多线程共享数据安全问题)的主要内容,如果未能解决你的问题,请参考以下文章