https://blog.csdn.net/qq_45464560/article/details/120804492

 

ThreadLocal介绍
官方介绍

ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能够保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。

白话:ThreadLocal的作用是提供线程内的局部变量,不同线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一线程内多个函数或组件之间一些公共变量传递的复杂度。

总结:

线程并发:在多线程并发场景下使用
传递数据:我们可以通过ThreadLocal在同一线程,不同组件中传递公共变量
线程隔离:每个线程的变量是独立的,不会相互影响(核心)
基本使用

常用方法:

在使用之前,我们先来认识几个ThreadLocal的常用方法

方法 介绍
ThreadLocal() 创建ThreadLocal对象(构造方法)
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量
简单案例:

public class ThreadLocalDemo1 {

private String demoVariable; // 变量

public String getDemoVariable() {
return demoVariable;
}

public void setDemoVariable(String demoVariable) {
this.demoVariable = demoVariable;
}

public static void main(String[] args) {
ThreadLocalDemo1 demo = new ThreadLocalDemo1();

for (int i=0; i<5; i++) {
Thread thread = new Thread(() -> {
demo.setDemoVariable(Thread.currentThread().getName()+”的数据”);
System.out.println(“———————————-“);
System.out.println(Thread.currentThread().getName()+”——>”+demo.getDemoVariable());
});
thread.setName(“线程”+i);
thread.start();
}
}

}

这段代码比较简单,首先一个类中声明一个变量(demoVariable),然后主方法中 创建五个线程同时操作同一个对象(demo)的setDemoVariable和getDemoVariable方法

代码输出结果:

———————————-
线程0——>线程0的数据
———————————-
———————————-
线程2——>线程2的数据
线程1——>线程2的数据
———————————-
———————————-
线程3——>线程3的数据
线程4——>线程3的数据

Process finished with exit code 0

看输出结果我们会发现数据比较混乱,有的线程获取的是其他线程的变量,例如:线程1——>线程2的数据
其实了解多线程的都知道,这段代码输出的结果是不确定的。多个线程操作同一对象的可变属性是线程不安全的。

那么怎样解决这个问题呢?
其实解决方案有多种,可以使用Jdk自带的synchronized锁、也可以使用基于AQS的锁、也可以利用CAS机制不加锁…这里这些都不是本篇文章的重点,接下来我们使用咱们的主角ThreadLocal来解决这个问题

上代码:

public class ThreadLocalDemo1 {
private static ThreadLocal<String> t1 = new ThreadLocal<>();

public String getDemoVariable() {
return t1.get(); // 从t1(ThreadLocal对象)中获取值
}

public void setDemoVariable(String demoVariable) {
t1.set(demoVariable); // 将传过来的参数绑定到t1(ThreadLocal对象)中
}

public static void main(String[] args) {
ThreadLocalDemo1 demo = new ThreadLocalDemo1();

for (int i=0; i<5; i++) {
Thread thread = new Thread(() -> {
demo.setDemoVariable(Thread.currentThread().getName()+”的数据”);
System.out.println(“———————————-“);
System.out.println(Thread.currentThread().getName()+”——>”+demo.getDemoVariable());
});
thread.setName(“线程”+i);
thread.start();
}
}
}

代码变化:

在类中增加了一个ThreadLocal对象——t1
删掉之前的demoVariable变量,因为我们不再操作它了
修改setDemoVariable方法:将传过来的参数绑定到t1(ThreadLocal对象)中
修改getDemoVariable方法:从t1(ThreadLocal对象)中获取值
代码输出结果:

———————————-
———————————-
线程1——>线程1的数据
线程0——>线程0的数据
———————————-
线程2——>线程2的数据
———————————-
线程3——>线程3的数据
———————————-
线程4——>线程4的数据

Process finished with exit code 0

此时我们会发现,每个线程输出的数据已经一一对应上了

ThreadLocal类与synchronized关键字区别
加锁解决方案

加锁解决方案的代码:

public class ThreadLocalDemo1 {

private String demoVariable; // 变量

public String getDemoVariable() {
return demoVariable;
}

public void setDemoVariable(String demoVariable) {
this.demoVariable = demoVariable;
}

public static void main(String[] args) {
ThreadLocalDemo1 demo = new ThreadLocalDemo1();

for (int i=0; i<5; i++) {
Thread thread = new Thread(() -> {
synchronized (demo) {
demo.setDemoVariable(Thread.currentThread().getName()+”的数据”);
System.out.println(“———————————-“);
System.out.println(Thread.currentThread().getName()+”——>”+demo.getDemoVariable());
}
});
thread.setName(“线程”+i);
thread.start();
}
}
}

主要代码:

synchronized (demo) {
demo.setDemoVariable(Thread.currentThread().getName()+”的数据”);
System.out.println(“———————————-“);
System.out.println(Thread.currentThread().getName()+”——>”+demo.getDemoVariable());
}

其实主要代码就是这一块儿,每个线程在操作demoVariable变量的时候,都为demo这个对象加上锁

代码输出结果:

———————————-
线程0——>线程0的数据
———————————-
线程1——>线程1的数据
———————————-
线程2——>线程2的数据
———————————-
线程3——>线程3的数据
———————————-
线程4——>线程4的数据
Process finished with exit code 0

从结果可以发现,加锁确实可以解决这个问题,但是在这里我们强调的是线程数据隔离的问题,并不是线程共享数据的问题,在这个案例中使用synchronized关键字是不合适的

ThreadLocal与synchronized的区别:

虽然ThreadLocal模式与synchronized关键字都用于处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同

synchronized:
原理:同步机制采用‘以时间换空间’的方式,只提供一份变量,让不同的线程排队访问
侧重点:多线程之间访问资源的同步
ThreadLocal:
原理:ThreadLocal采用‘以空间换时间’的方式,为每个线程都提供一份变量的副本,从而实现同时访问而不互相干扰
侧重点:多线程中让每个线程之间的数据相互隔离
ThreadLocal的内部结构
JDK8中ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal示例本身,value才是真正要存储的值Object。

具体的过程是这样的:

每个Thread线程内部都有一个Map(ThreadLocalMap)
Map里面存储的是key,value形式的数据,key:ThreadLocal对象,value:线程的变量副本
Thread内部的Map是由ThreadLocal维护的,由ThreadLocal负责向map获取和设置线程的变量值
对于不同的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,形成了副本的隔离

ThreadLocal的核心方法源码
基于ThreadLocal的内部结构,我们继续分析它的核心方法源码,更深入地了解其操作原理。
除了构造方法之外,ThreadLocal对外暴露的方法有以下4个:

方法 介绍
protected T initialValue() 返回当前线程局部变量的初始值(return null)
public void set(T value) 设置当前线程绑定的局部变量
public T get() 获取当前线程绑定的局部变量
public void remove() 移除当前线程绑定的局部变量
set方法

源码和对应的中文注释:

/**
* 设置当前线程对应的ThreadLocal的值
*
* @param value 将要保存在当前线程对应的ThreadLocal的值
*/
public void set(T value) {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
}

/**
* 获取当前线程Thread对应维护的ThreadLocalMap
*
* @param t the current thread 当前线程
* @return the map 对应维护的ThreadLocalMap
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
/**
*创建当前线程Thread对应维护的ThreadLocalMap
*
* @param t 当前线程
* @param firstValue 存放到map中第一个entry的值
*/
void createMap(Thread t, T firstValue) {
//这里的this是调用此方法的threadLocal
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

代码执行流程:

​ A. 首先获取当前线程,并根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则将参数设置到Map中(当前ThreadLocal的引用作为key)

​ C. 如果Map为空,则给该线程创建 Map,并设置初始值

get方法

源码和对应的中文注释:

/**
* 返回当前线程中保存ThreadLocal的值
* 如果当前线程没有此ThreadLocal变量,
* 则它会通过调用{@link #initialValue} 方法进行初始化值
*
* @return 返回当前线程对应此ThreadLocal的值
*/
public T get() {
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 如果此map存在
if (map != null) {
// 以当前的ThreadLocal 为 key,调用getEntry获取对应的存储实体e
ThreadLocalMap.Entry e = map.getEntry(this);
// 对e进行判空
if (e != null) {
@SuppressWarnings(“unchecked”)
// 获取存储实体 e 对应的 value值
// 即为我们想要的当前线程对应此ThreadLocal的值
T result = (T)e.value;
return result;
}
}
/*
初始化 : 有两种情况有执行当前代码
第一种情况: map不存在,表示此线程没有维护的ThreadLocalMap对象
第二种情况: map存在, 但是没有与当前ThreadLocal关联的entry
*/
return setInitialValue();
}

/**
* 初始化
*
* @return the initial value 初始化后的值
*/
private T setInitialValue() {
// 调用initialValue获取初始化的值
// 此方法可以被子类重写, 如果不重写默认返回null
T value = initialValue();
// 获取当前线程对象
Thread t = Thread.currentThread();
// 获取此线程对象中维护的ThreadLocalMap对象
ThreadLocalMap map = getMap(t);
// 判断map是否存在
if (map != null)
// 存在则调用map.set设置此实体entry
map.set(this, value);
else
// 1)当前线程Thread 不存在ThreadLocalMap对象
// 2)则调用createMap进行ThreadLocalMap对象的初始化
// 3)并将 t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中
createMap(t, value);
// 返回设置的值value
return value;
}

代码执行流程:

​ A. 首先获取当前线程, 根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则在Map中以ThreadLocal的引用作为key来在Map中获取对应的Entry e,否则转到D

​ C. 如果e不为null,则返回e.value,否则转到D

​ D. Map为空或者e为空,则通过initialValue函数获取初始值value,然后用ThreadLocal的引用和value作为firstKey和firstValue创建一个新的Map

remove方法

源码和对应的中文注释:

/**
* 删除当前线程中保存的ThreadLocal对应的实体entry
*/
public void remove() {
// 获取当前线程对象中维护的ThreadLocalMap对象
ThreadLocalMap m = getMap(Thread.currentThread());
// 如果此map存在
if (m != null)
// 存在则调用map.remove
// 以当前ThreadLocal为key删除对应的实体entry
m.remove(this);
}

代码执行流程:
​ A. 首先获取当前线程,并根据当前线程获取一个Map

​ B. 如果获取的Map不为空,则移除当前ThreadLocal对象对应的entry

initialValue方法

源码和对应的中文注释:

/**
* 返回当前线程对应的ThreadLocal的初始值

* 此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时
* 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。
* 通常情况下,每个线程最多调用一次这个方法。
* * <p>这个方法仅仅简单的返回null {@code null};
* 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值,
* 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法
* 通常, 可以通过匿名内部类的方式实现
* * @return 当前ThreadLocal的初始值
*/
protected T initialValue() {
return null;
}

此方法的作用是 返回该线程局部变量的初始值。

这个方法是一个延迟调用方法,从上面的代码我们得知,在set方法还未调用而先调用了get方法时才执行,并且仅执行1次。
这个方法缺省实现直接返回一个null。
如果想要一个除null之外的初始值,可以重写此方法。(备注: 该方法是一个protected的方法,显然是为了让子类覆盖而设计的)

 

原文地址:http://www.cnblogs.com/cuipengchong/p/16853630.html

1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长! 2. 分享目的仅供大家学习和交流,请务用于商业用途! 3. 如果你也有好源码或者教程,可以到用户中心发布,分享有积分奖励和额外收入! 4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解! 5. 如有链接无法下载、失效或广告,请联系管理员处理! 6. 本站资源售价只是赞助,收取费用仅维持本站的日常运营所需! 7. 如遇到加密压缩包,默认解压密码为"gltf",如遇到无法解压的请联系管理员! 8. 因为资源和程序源码均为可复制品,所以不支持任何理由的退款兑现,请斟酌后支付下载 声明:如果标题没有注明"已测试"或者"测试可用"等字样的资源源码均未经过站长测试.特别注意没有标注的源码不保证任何可用性