Spring是如何处理Bean多线程下的并发问题的? ----- ThreadLocal
ThreadLocal天生为解决相同变量的访问冲突问题, 所以这个对于spring的默认单例bean的多线程访问是一个完美的解决方案。spring也确实是用了ThreadLocal来处理多线程下相同变量并发的线程安全问题。
1. ThreadLocal的简介
那么看看jdk是怎么说的:此类提供线程局部变量,这些变量与普通变量不同,每个线程都有自己的变量,通过ThreadLocal的get或者set方法访问,有独立初始化的变量副本。ThreadLocal实例通常是希望将状态与线程关联的类中的私有静态字段。
2.ThreadLocal的实现原理
ThreadLocal类中提供了几个方法:
1.public T get() { }
2.public void set(T value) { }
3.public void remove() { }
4.protected T initialValue(){ }
public T get()
public T get() {
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals变量不为null,就可以在map中查找到本地变量的值
if (map != null) {
//根据ThreadLocal的弱引用的key 查询ThreadLocalMap的Value值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果threadLocals为null,则初始化当前线程的threadLocals变量
return setInitialValue();
}
private T setInitialValue() {
//此处是返回一个null
T value = initialValue();
//获取当前线程
Thread t = Thread.currentThread();
//获取当前线程的threadLocals变量
ThreadLocalMap map = getMap(t);
//如果threadLocals不为null,就直接添加本地变量,key为当前线程,值为添加的本地变量值
if (map != null)
map.set(this, value);
//如果threadLocals为null,说明首次添加,需要首先创建出对应的map
else
createMap(t, value);
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocal.ThreadLocalMap(this, firstValue);
}
public void set(T value)
//话不多说,这个容易理解
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocal.ThreadLocalMap map = getMap(t);
if (map != null)
//这个是把ThreadLocal当做key,添加的属性值为value
map.set(this, value);
else
createMap(t, value);
}
public void remove()
使用完ThreadLocal要积极调用此方法,防止内存泄漏
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
//当前线程根据ThrealLocal为key值删除
m.remove(this);
}
protected T initialValue()
对ThreadLocal初始化 使用时要自己重写此方法设置默认值
protected T initialValue() {
return null;
}
ThreadLocal的使用
1、我们先来看看jdk给的例子:
public class ThreadId {
private static final AtomicInteger nextId = new AtomicInteger(0);
private static final ThreadLocal<Integer> threadId = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return nextId.getAndIncrement();
}
};
public static int get() {
return threadId.get();
}
}
class TestID{
public static void main(String[] args) {
int i = ThreadId.get();
System.out.println(i);//0
}
}
执行此案例可以看到运行结果:是0 ,因为上面重写了initialValue() ,如果没有重写此方法,则返回结果为null。
2、验证线程的隔离性:
public class ThreadLocalDemo2 {
public static ThreadLocal t1 = new ThreadLocal();
}
class ThreadA extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("TreadA " + (i + 1));
System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class ThreadB extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("TreadB " + (i + 1));
System.out.println("Thread get Value = " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Test {
public static void main(String[] args) {
try {
ThreadA a = new ThreadA();
ThreadB b = new ThreadB();
a.start();
b.start();
for (int i = 0; i < 100; i++) {
ThreadLocalDemo2.t1.set("Main " + (i + 1));
System.out.println("Main get Value " + ThreadLocalDemo2.t1.get());
Thread.sleep(200);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行此案例可以看到运行结果:ThreadA线程、ThreadB线程、和Main线程都各自获取自己线程存储的值。 但是调用的却是同一个ThreadLocal对象,那么得出结论,ThreadLocal可以实现线程间的隔离。
3、ThreadLocal的不可继承性:
由上面的例子可以看出,main线程和子线程间的数据是隔离的,并不能实现继承,那么ThreadLocal的子类InheritableThreadLocal提供了这种问题的解决方案。
下面我们通过一个案例了解一下:
public class ThreadLocalDemo6 {
public static InheritableThreadLocal<Long> inheritableThreadLocal = new InheritableThreadLocal() {
@Override
protected Long initialValue() {
return new Date().getTime();
}
};
}
class ThreadA6 extends Thread {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
System.out.println("在ThreadA 线程中取值 : " + ThreadLocalDemo6.inheritableThreadLocal.get());
Thread.sleep(100);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Test6 {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
System.out.println("在线程main 中取值为: " + ThreadLocalDemo6.inheritableThreadLocal.get());
}
Thread.sleep(5000);
ThreadA6 threadA6 = new ThreadA6();
threadA6.start();
}
}
执行此案例可以看到运行结果:main线程设置的属性值,在子线程中可以获取到,那么可以得出InheritableThreadLocal 具有继承的特性。
从ThreadLocalMap看ThreadLocal使用不当的内存泄漏问题
分析ThreadLocalMap内部实现
ThreadLocalMap的内部是一个Entry数组,下面我们来看一下entry的源码
/**
* Entry是继承自WeakReference的一个类,该类中实际存放的key是
* 指向ThreadLocal的弱引用和与之对应的value值(该value值
* 就是通过ThreadLocal的set方法传递过来的值)
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** value就是和ThreadLocal绑定的 */
Object value;
//k:ThreadLocal的引用,被传递给WeakReference的构造方法
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
由上可知,ThreadLocal是弱引用,那么这个引用抗不过一次GC,key可以被回收,那么value 确不能被回收,这就可能造成内存泄漏(因为这个时候ThreadLocalMap会存在key为null但是value不为null的entry项)。
规范使用ThreadLocal
在使用ThreadLocal(set()或者get()) 后,如果不在使用则要及时使用remove()方法清理资源,避免内存泄漏的问题。
本文作者:张三Blog
本文链接:https://www.cnblogs.com/zhangsan-plus/p/16503311.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了