一、ThreadLocal
多线程访问同一个共享变量时特别容易出现并发问题,特别是在多个线程需要对一个共享变量进行写入时。为了保证线程安全,一般使用者在访问共享变量时需要进行适当的同步,如图 1-3所示。
同步的措施一般是加锁,这就需要使用者对锁有一定的了解,这显然加重了使用者的负担。那么有没有一种方式可以做到,当创建一个变量后,每个线程对其进行访问的时候访问的是自己线程的变量呢?其实ThreadLocal 就可以做这件事情,虽然ThreadLocal 并不是为了解决这个问题而出现的。
ThreadLocal是JDK包提供的,它提供了线程本地变量,也就是如果你创建了一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个本地副本。当多个线程操作这个变量时,实际操作的是自己本地内存里面的变量,从而避免了线程安全问题。创建一个 ThreadLocal变量后,每个线程都会复制一个变量到自己的本地内存,如图 1-4所示。
Threadlocal 使用示例
public class ThreadLocalExample {
// 创建一个ThreadLocal变量来保存每个线程的计数器
private static final ThreadLocal<Integer> threadLocalCounter = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
// 创建并启动两个线程
Thread thread1 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
// 获取当前线程的计数器,并增加
int counter = threadLocalCounter.get();
counter++;
threadLocalCounter.set(counter);
System.out.println("Thread 1 counter: " + threadLocalCounter.get());
}
} finally {
// 清理ThreadLocal变量
threadLocalCounter.remove();
}
});
Thread thread2 = new Thread(() -> {
try {
for (int i = 0; i < 5; i++) {
// 获取当前线程的计数器,并增加
int counter = threadLocalCounter.get();
counter++;
threadLocalCounter.set(counter);
System.out.println("Thread 2 counter: " + threadLocalCounter.get());
}
} finally {
// 清理ThreadLocal变量
threadLocalCounter.remove();
}
});
// 启动线程
thread1.start();
thread2.start();
// 等待线程执行完成
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在这个示例中,我们创建了两个Thread对象,并为它们分别分配了不同的任务。每个任务都在一个try-finally块中执行,以确保无论任务是否成功完成,ThreadLocal变量最终都会被清理。
Threadlocal 的实现原理
首先看 ThreadLocal 关类 类图结构,如图1-5所示
由该图可知,Thread 类中有一个threadLocals 和一个 inheritableThreadLocals,它们都是 ThreadLocalMap 类型的变量,而ThreadLocalMap是一个定制化的Hashmap。在默认情况下,每个线程中的这两个变量都为 nul,只有当前线程第一次调用 ThreadLocal 的 set 或者 get方法时才会创建它们。
其实每个线程的本地变量不是存放在ThreadLocal 实例里面,而是存放在调用线程的threadLocals 变量里面。也就是说,ThreadLocal 类型的本地变量存放在具体的线程内存空间中。ThreadLocal就是一个工具壳,它通过set方法把value值放入调用线程的threadocals里面并存放起来,当调用线程调用它的get方法时,再从当前线程的 threadLocals 变量里面将其拿出来使用。如果调用线程一直不终止,那么这个本地变量会一直存放在调用线程的threadLocals 变量里面,所以当不需要使用本地变量时可以通过调用ThreadLocal变量的remove方法,从当前线程的threadLocals里面删除该本地变量。
另外,Thread 里面的threadLocals为何被设计为map 结构?很明显是因为每个线程可以关联多个 ThreadLocal 变量。
下面简单分析 ThreadLocal set get remove 方法的实现逻辑
1. void set(T value)
public void set(T value) {
//(1)获取当前线程
Thread t = Thread.currentThread();
//(2)将当前线程作为key,去查找对应的线程变量,找到则设置
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
//(3)第一次调用就创建当前线程对应的HashMap
createMap(t, value);
}
代码 (1)先获取调用线程,然后使用当前 线程作为 参数 调用 getMap(t )方法 getMap(Thread t)的代码如下
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以看到, getMap(t)的作用是获取线程 自己的变 threadLocals, threadlocal 变量被绑定到了线程的成员变量上。
如果 getMap(t)的返回值不为空,则把 value 值设置到 threadLocals 中,也就是把当前值放入当前线程的内存变量 threadLocals中, threadLocals 是一个 HashMap 结构,其中key就是当前 ThreadLocal 实例对象引用, value 是通过set方法传递的值。
如果 getMap(t)返回空值则说明是第一次调 set 方法,这时创建当前线程的 threadLocals 量。下面来看 createMap(t, value)做什么。
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
它创建当前线程的 threadLocals 变量。
2. T get()
public T get() {
//(4)获取当前线程
Thread t = Thread.currentThread();
//(5)获取当前threadlocals变量
ThreadLocalMap map = getMap(t);
//(6)如果threadlocals不为null,则返回对应本地变量的值
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//(7)threadlocals为空则初始化当前线程的threadLocals成员变量
return setInitialValue();
}
代码( 4)首先获取当前线程实例,如果当线程的 threadLocals 变量不为 null ,则直接返回当前线程绑定的本地变量,否则执行代码(7)进行初始化。 setInitialValue()的代码如下。
private T setInitialValue() {
//(8)初始化为null
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
//(9)如果当前线程的threadLocals变量不为空
if (map != null)
map.set(this, value);
else
//(10)如果当前线程的threadLocals变量为空
createMap(t, value);
return value;
}
protected T initialValue() {
return null;
}
如果当前线程的 threadLocals变量不为空,则设置当前线程的本地变量值为null,否则调用 createMap 方法建当前线程的 createMap变量。
3. void remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
如以上代码所示,如果当前线程的threadLocals 变量不为空,则删除当前线程中指定ThreadLocal实例的本地变量。
总结
如图1-6所示,在每个线程内部都有一个名为threadLocals的成员变量,该变量的类型为 HashMap,其中 key 为我们定义的 ThreadLocal 变量的 this引用,value 则为我们使用set方法设置的值。每个线程的本地变量存放在线程自己的内存变量threadLocals中,如果当前线程一直不消亡,那么这些本地变量会一直存在,所以可能会造成内存溢出,因此使用完毕后要记得调用 ThreadLocal的remove 方法删除对应线程的threadLocals中的本地变量。
ThreadLoal 不支持继承性
首先看一个例子
public class TestThreadLocal {
//(1)创建线程变量
public static ThreadLocal<String> threadlocal = new ThreadLocal<String>();
public static void main(String[] args) {
threadlocal.set("hello world");
//(3)启动子线程
Thread thread = new Thread(new Runnable() {
public void run() {
//(4)子线程输出线程变量的值
System.out.println("thread:" + threadlocal.get());
}
});
thread.start();
//(5)主线程输出线程变量的值
System.out.println("main:" + threadlocal.get());
}
}
也就是说,同一个 ThreadLocal 变量在父线程中被设置值后,在子线程中是获取不到的。根据上节的介绍,这应该是正常现象,因为在子线程thread里面调用get方法时当前线程为thread 线程,而这里调用set方法设置线程变量的是 main 线程,两者是不同的线程,自然子线程访问时返回nul。那么有没有办法让子线程能访问到父线程中的值?答案是有的,请看下一篇文章。
标签:set,JAVA,变量,Thread,ThreadLocal,线程,threadLocals From: https://blog.csdn.net/weixin_45826852/article/details/142094082