一文看懂Java中的ThreadLocal源码和注意事项( 二 )

注意,remove() 方法只会删除当前线程的 ThreadLocalMap 中与该 ThreadLocal 对象对应的 entry,不会影响其他线程的值 。
三、ThreadLocalMap的Entry为什么是弱引用?ThreadLocal的内部类ThreadLocalMap源码可以看到,它的Entry继承WeakReference,是一个弱引用的类
/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread.To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/static class ThreadLocalMap {/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = https://www.isolves.com/it/cxkf/yy/JAVA/2023-04-12/v;}}.........}其实使用弱引用是为了避免内存泄漏问题 。
ThreadLocalMap 中的 key 为 ThreadLocal 对象,value 为线程本地变量对应的副本 。当一个线程结束时,如果不显式地清理 ThreadLocalMap 中该线程对应的 Entry 对象,那么这些 Entry 对象及其对应的 value 副本会一直存在于内存中,就会导致内存泄漏问题 。
如果 ThreadLocalMap 中使用的是强引用,那么即使线程已经结束,由于这些 Entry 对象仍然被 ThreadLocalMap 引用着,垃圾回收器也无法将这些 Entry 对象回收掉,从而导致内存泄漏 。
因此,为了避免这种情况,ThreadLocalMap 中使用了 WeakReference 来引用 ThreadLocal 对象 。WeakReference 对象具有弱引用特性,当 ThreadLocal 变量没有被其他对象引用时,就可以被垃圾回收器回收,同时 ThreadLocalMap 中对应的 Entry 对象也会被自动删除,从而避免了内存泄漏问题 。
特别需要注意的是,虽然 ThreadLocalMap 中的 Entry 对象是弱引用,但是 value 部分却是强引用,因此当 ThreadLocal 变量被垃圾回收器回收后,对应的 value 副本仍然会一直存在于内存中,需要开发者手动清理 。因此,在使用 ThreadLocal 时,需要注意在不需要使用 ThreadLocal 变量时,及时调用 remove() 方法进行清理 。
四、代码示例和解释下面是一个简单的ThreadLocal使用示例:
public class ThreadLocalTest {private static ThreadLocal<Integer> threadLocal = new ThreadLocal<Integer>() {@Overrideprotected Integer initialValue() {return 0;}};public static void main(String[] args) {for (int i = 0; i < 5; i++) {new Thread(new Runnable() {@Overridepublic void run() {int num = threadLocal.get();System.out.println(Thread.currentThread().getName() + " get num=" + num);threadLocal.set(num + 1);System.out.println(Thread.currentThread().getName() + " set num=" + (num + 1));System.out.println(Thread.currentThread().getName() + " get num=" + threadLocal.get());}}).start();}}}上面的代码中,我们使用了一个静态变量threadLocal,它是一个ThreadLocal对象 。我们在main方法中创建了5个线程,每个线程中都调用了threadLocal的get()方法来获取变量值,并输出线程名和变量值 。然后,调用了threadLocal的set()方法来修改变量值,并输出线程名和修改后的变量值 。最后再次调用get()方法来获取变量值,并输出线程名和变量值 。
Thread-1 get num=0Thread-1 set num=1Thread-1 get num=1Thread-4 get num=0Thread-4 set num=1Thread-4 get num=1Thread-3 get num=0Thread-3 set num=1Thread-3 get num=1Thread-2 get num=0Thread-2 set num=1Thread-2 get num=1Thread-0 get num=0Thread-0 set num=1Thread-0 get num=1我们可以看到,虽然这个变量是static的,但是每个线程都有自己的变量副本,它们互不干扰 。这就避免了线程安全问题的出现 。
五、注意事项在使用ThreadLocal时,需要注意以下几点:
1.内存泄漏问题:ThreadLocal 中的变量可以在程序执行结束后一直存在于内存中,如果不及时清理,会导致内存泄漏问题 。因此,在使用完 ThreadLocal 变量之后,需要手动调用 remove() 方法,将变量和当前线程绑定的副本从 ThreadLocalMap 中移除,并释放相关内存空间 。
2.资源占用问题:ThreadLocal 对象本身也是一个对象,使用过多的 ThreadLocal 对象会占用大量的内存空间 。因此,应该根据实际情况,合理地使用和管理 ThreadLocal 对象,避免过度使用 。


推荐阅读