ThreadLocal类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过get和set方法访问)时能够保证各个线程的变量相对独立于其他线程内的变量。ThreadLocal实例通常来说都是 private static类型的,用于关联线程和线程上下文。
白话: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();
}
}
}
代码变化:
代码输出结果:
----------------------------------
----------------------------------
线程1------>线程1的数据
线程0------>线程0的数据
----------------------------------
线程2------>线程2的数据
----------------------------------
线程3------>线程3的数据
----------------------------------
线程4------>线程4的数据
Process finished with exit code 0
此时我们会发现,每个线程输出的数据已经一一对应上了
加锁解决方案的代码:
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关键字都用于处理多线程并发访问变量的问题,不过两者处理问题的角度和思路不同
synchronized:
原理:同步机制采用‘以时间换空间’的方式,只提供一份变量,让不同的线程排队访问
侧重点:多线程之间访问资源的同步
ThreadLocal:
原理:ThreadLocal采用‘以空间换时间’的方式,为每个线程都提供一份变量的副本,从而实现同时访问而不互相干扰
侧重点:多线程中让每个线程之间的数据相互隔离
JDK8中ThreadLocal的设计是:每个Thread维护一个ThreadLocalMap,这个Map的key是ThreadLocal示例本身,value才是真正要存储的值Object。
具体的过程是这样的:
基于ThreadLocal的内部结构,我们继续分析它的核心方法源码,更深入地了解其操作原理。
除了构造方法之外,ThreadLocal对外暴露的方法有以下4个:
方法 | 介绍 |
---|---|
protected T initialValue() | 返回当前线程局部变量的初始值(return null) |
public void set(T value) | 设置当前线程绑定的局部变量 |
public T get() | 获取当前线程绑定的局部变量 |
public void remove() | 移除当前线程绑定的局部变量 |
源码和对应的中文注释:
/** * 设置当前线程对应的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,并设置初始值
源码和对应的中文注释:
/** * 返回当前线程中保存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
源码和对应的中文注释:
/** * 删除当前线程中保存的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
源码和对应的中文注释:
/** * 返回当前线程对应的ThreadLocal的初始值 * 此方法的第一次调用发生在,当线程通过get方法访问此线程的ThreadLocal值时 * 除非线程先调用了set方法,在这种情况下,initialValue 才不会被这个线程调用。 * 通常情况下,每个线程最多调用一次这个方法。 * * <p>这个方法仅仅简单的返回null {@code null}; * 如果程序员想ThreadLocal线程局部变量有一个除null以外的初始值, * 必须通过子类继承{@code ThreadLocal} 的方式去重写此方法 * 通常, 可以通过匿名内部类的方式实现 * * @return 当前ThreadLocal的初始值 */
protected T initialValue() {
return null;
}
此方法的作用是 返回该线程局部变量的初始值。
未完待续。。。
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_45464560/article/details/120804492
内容来源于网络,如有侵权,请联系作者删除!