文章目录
- ⚽ThreadLocal
- 🎉入门案例
- 🎈ThreadLocal在线程中怎么存储的
- 🎗为什么会造成内存泄漏?
- 🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?
- 🔔补充说明
- Java中引用类型分类
- 内存泄漏和内存溢出区别
⚽ThreadLocal
用来为每个线程提供独立的变量副本的
🎉入门案例
使用案例:
java">public class ThreadLocalMultipleExample {private static final ThreadLocal<Integer> threadLocal1 = ThreadLocal.withInitial(() -> 1);private static final ThreadLocal<String> threadLocal2 = ThreadLocal.withInitial(() -> "Hello");public static void main(String[] args) {// 线程A设置值Thread threadA = new Thread(() -> {threadLocal1.set(10);threadLocal2.set("Thread A");System.out.println("Thread A - local1: " + threadLocal1.get()); // 10System.out.println("Thread A - local2: " + threadLocal2.get()); // Thread A});// 线程B设置值Thread threadB = new Thread(() -> {System.out.println("Thread B - local1: " + threadLocal1.get()); // 1System.out.println("Thread B - local2: " + threadLocal2.get()); // Hello});threadA.start();threadB.start();}
}
运行结果:可以看到虽然线程A修改了变量的值,但是在线程B中变量的值还是初始给的值,因为这两个变量在每个线程中都有自己的副本。
java">Thread A - local1: 10
Thread A - local2: Thread A
Thread B - local1: 1
Thread B - local2: Hello
🎈ThreadLocal在线程中怎么存储的
先来了解ThreadLocal在Thread时怎么存储的。
我们先看Thread
的源码,里面有个ThreadLocalMap
类型的变量。
java">public class Thread implements Runnable {ThreadLocal.ThreadLocalMap threadLocals = null;
}
从下面ThreadLocal
的部分源码中可以看出,这个ThreadLocalMap
类是ThreadLocal
类中静态内部类。我们可以把ThreadLocalMap
当做一个Map
,其中ThreadLocal
是key
,存储的值就是value
。
可以看到ThreadLocal
的set、get
方法,都是用Thread.currentThread()
获取当前线程后,拿到每个线程自己独有的ThreadLocalMap
之后进行读写操作,所以这里保证了每个线程都有自己的ThreadLocal
副本。
java">public class ThreadLocal<T> {// ......省略部分代码static class ThreadLocalMap {static class Entry extends WeakReference<ThreadLocal<?>> {Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}}// ......省略部分代码public T get() {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}return setInitialValue();}// ......省略部分代码public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null) {map.set(this, value);} else {createMap(t, value);}}
}
ThreadLocalMap
和HashMap
在存储结构上有些不同,HashMap是数组+链表(+红黑树)的形式,但是ThreadLocalMap是纯数组的形式,内部只有一个Entry[] table
数组,其中一个Entry就是一个键值对。
使用ThreadLocal时需要注意避免出现内存泄漏问题。
🎗为什么会造成内存泄漏?
我们从源码中可以看出ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal不存在外部强引用时,Key(ThreadLocal)势必会被GC回收,这样就会导致ThreadLocalMap中key为null, 而value还存在着强引用,只有当thead线程退出以后,value的强引用链条才会断掉。意味着这个线程一直不结束的话,这个value就一直无法回收,造成内存泄漏,这种情况一般发生在使用线程池的场景中,因为里面的线程正常情况下会一直存活。
在平时使用ThreadLocal类时,要避免内存泄漏问题,可以在线程处理完任务后,使用threadLocal.remove()
方法,移除当前threadLocal
。
🎃ThreadLocalMap的key使用强引用和弱引用有什么区别呢?
- key 使用强引用:当ThreadLocalMap的key为强引用,发生GC时,因为ThreadLocalMap还持有ThreadLocal的强引用,同时ThreadLocalMap和Thread生命周期相同,如果没有手动删除,ThreadLocal不会被回收,导致Entry内存泄漏。
- key 使用弱引用:当ThreadLocalMap的key为弱引用,发生GC时,由于ThreadLocalMap持有的是ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。当key为null,在下一次其他ThreadLocal调用他们的set(),get(),remove()方法的时候都会清除key为null对应的value值。
🔔补充说明
Java中引用类型分类
- 强引用:我们常常 new 出来的对象就是强引用类型,只要强引用存在,垃圾回收器将永远不会回收被引用的对象,哪怕内存不足的时候
- 软引用:使用 SoftReference 修饰的对象被称为软引用,软引用指向的对象在内存要溢出的时候被回收
- 弱引用:使用 WeakReference 修饰的对象被称为弱引用,只要发生垃圾回收,若这个对象只被弱引用指向,那么就会被回收
- 虚引用:虚引用是最弱的引用,在 Java 中使用 PhantomReference 进行定义。虚引用中唯一的作用就是用队列接收对象即将死亡的通知
内存泄漏和内存溢出区别
- 内存泄漏 是因为程序没有释放不再使用的内存,导致内存逐渐积累,最终可能引起内存溢出。
- 内存溢出 是当程序请求的内存超出了系统可分配的最大值时,操作系统无法满足内存请求,从而导致程序崩溃。