目录
目录
前置:
ThreadLocal——线程本地存储。也就是线程本地变量的管理者,一般用于实现线程隔离(数据隔离),在线程中使用它存储数据,仅在当前线程访问使用
其实,它仅仅是一个管理者,真正存储数据的不是它,而是一个叫做ThreadLocalMap的对象,每个线程都有一个自己的TreadLocalMap对象(在Thread中对象名为:threadLocals或者inheritableThreadLocals)。
场景:
比如JDBC,我们每个线程进行数据库操作,连接都有自己独有的,而不会出现a连接到b上面
实现:
这里我们会体现他是怎么做到线程隔离的
ThreadLocal<String> localName = new ThreadLocal();
localName.set("张三");
String name = localName.get();
localName.remove();
set源代码
会发现,设置值->会先获取当前线程,根据当前线程得到它的ThreadLocalMap,然后通过ThreadLocalMap赋值
那么ThreadLocalMap又是啥呢?
从源码可知,ThreadLocalMap其实就是Thread中的一个叫threadLocals的变量获取的
所以说,数据的逻辑是在Thread当前线程中,而不是在ThreadLocal,它相当于只是一个管理类
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); // 不为空set
else
createMap(t, value); // 为空创建一个map对象
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
可以发现每个Thread都维护自己的ThreadLocals变量,当每个线程创建ThreadLocal时候,实际上数据是存在ThreadLocals里面的,从而实现隔离,所以说数据隔离的逻辑在Thread中;
public class Thread implements Runnable {
……
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
/*
* InheritableThreadLocal values pertaining to this thread. This map is
* maintained by the InheritableThreadLocal class.
*/
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
……
ThreadLocalMap的底层结构
类似于HashMap,其实不然,源码可知是一个Entry->就是一个键值对对象,继承WeakReference弱引用,key是ThreadLocal的引用,但是存储数据的逻辑是给到Thread本身,就像在公司做事一样,我现在在这个公司做事,我是Thread,事情是value,在的地方是ThreadLocal
(22条消息) Java中Map集合中的Entry对象_宇智波爱编程的博客-CSDN博客_entry
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
……
}
其实可以发现,本身ThreadLocalMap维护的是一个哈希表一样的结构,本质也就是数组
那么ThreadLocalMap为什么要用数组呢?
之前我们说过,ThreadLocal就像是维护了一个地方,存储值的逻辑还是在Thread本身,将值存在ThreadLocalMap中,而ThreadLocalMap维护的是一个哈希表,每个Entry都是一个键值对,键就是在哪做事(ThreadLocal),对就是值,所以说我们Thread是可以做很多事的,做的地方也可以不一样(可以创建多个ThreadLocal对象),只是说逻辑都在当前Thread本身上,所以肯定用数组来存;
场景:开发过程中可以一个线程可以有多个TreadLocal来存放不同类型的对象的,但是他们都将放到你当前线程的ThreadLocalMap里;
static class ThreadLocalMap {
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
private static final int INITIAL_CAPACITY = 16;
private Entry[] table;
那么set对象存放在哪里?
(22条消息) JVM-01(阶段性学习)_Fairy要carry的博客-CSDN博客
因为是线程,众所周知,每个线程都会有自己的栈内存,,其存储的变量只能在其所属线程中可见,即栈内存可以理解成线程的私有内存,而堆内存中的对象对所有线程可见,堆内存中的对象可以被所有线程访问。
注:当然我们new 的ThreadLocal实例虽然被Thread给引用,进行了一个持有的操作,但是new嘛,所以也是位于堆上的;
如何共享ThreadLocal数据,让多个线程访问
使用InheritableThreadLocal
可以实现多个线程访问ThreadLocal的值,我们在主线程中创建一个InheritableThreadLocal
的实例,然后在子线程中得到这个InheritableThreadLocal
实例设置的值。
private void test() {
final ThreadLocal threadLocal = new InheritableThreadLocal();
threadLocal.set("帅得一匹");
Thread t = new Thread() {
@Override
public void run() {
super.run();
Log.i( "张三帅么 =" + threadLocal.get());
}
};
t.start();
}
内存泄露问题
2024更新
实际上,ThreadLocalMap 使用了弱引用来避免内存泄漏。当一个线程结束时,其对应的 ThreadLocalMap 会被垃圾回收,进而清除所有的 Entry 对象。由于 Entry 对象使用的是弱引用,如果该对象在其他地方没有被引用,那么它会在垃圾回收过程中被释放掉。这样就可以避免 ThreadLocal 导致的内存泄漏问题。
---------------------------------------------------------------------------------------------------------------------
我们首先回顾一下ThreadLocalMap,里面的Entry继承了WeakReference弱引用
说明ThreadLocal在保存数据时会吧自己当做key存在ThreadLocalMap中,也就是说ThreadLocal会被ThreadLocalMap持有引用(也就是Thread当前线程),正常情况应该是key和value都应该被外界强引用才对,但是现在key被设计成WeakReference弱引用了
(22条消息) 垃圾回收流程-简单叙述_Fairy要carry的博客-CSDN博客_垃圾回收过程
导致问题:
ThreadLocal因为在Thread本身逻辑里面是没有被强引用的,所以发生GC时候会被回收,但是value会暴露出来
这就导致了一个问题,ThreadLocal在没有外部强引用时,发生GC时会被回收,如果创建ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露;
如何解决:
最后remove即可,就可以把值给清空了
ThreadLocal<String> localName = new ThreadLocal();
try {
localName.set("张三");
……
} finally {
localName.remove();
}
remove逻辑:把所有对应的值都置空
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear();
expungeStaleEntry(i);
return;
}
}
}
那么问题来了,为什么要把ThreadLocalMap的key设计成弱引用?
key不设置成弱引用的话就会造成和entry中value一样内存泄漏的场景。
ThreadLocal练习
//主线程中
ThreadLocal<String> k1 = new ThreadLocal<>();
ThreadLocal<Integer> k2 = new ThreadLocal<>();
ThreadLocal<Object> k3 = new ThreadLocal<>();
k1.set("main thread");
k2.set(1000);
k3.set("hallo~");
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("thread1:"+k1.get());
System.out.println("thread1:"+k2.get());
System.out.println("thread1:"+k3.get());
k1.set("123");
k2.set(1);
k3.set("sss");
System.out.println("thread1:"+k1.get());
System.out.println("thread1:"+k2.get());
System.out.println("thread1:"+k3.get());
System.out.println();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread2:"+k1.get());
System.out.println("thread2:"+k2.get());
System.out.println("thread2:"+k3.get());
k1.set("abc");
k2.set(2);
k3.set(k3);
System.out.println("thread2:"+k1.get());
System.out.println("thread2:"+k2.get());
System.out.println("thread2:"+k3.get());
System.out.println();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread3:"+k1.get());
System.out.println("thread3:"+k2.get());
System.out.println("thread3:"+k3.get());
k1.set("haha");
k2.set(10);
k3.set(new Object());
System.out.println("thread3:"+k1.get());
System.out.println("thread3:"+k2.get());
System.out.println("thread3:"+k3.get());
System.out.println();
}
});
thread1.start();
thread2.start();
thread3.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:"+k1.get());
System.out.println("main:"+k2.get());
System.out.println("main:"+k3.get());