ThreadLocal那点事

目录

1.ThreadLocal原理

2.ThreadLocal内存泄漏

3.ThreadLocal最佳实践

4.FastThreadLocal原理

5.FastThreadLocal最佳实践

6.ThreadLocal与FastThreadLocal性能比较

7.总结


1.ThreadLocal原理

ThreadLocal是用在多线程中,用于保存当前线程的上下文信息。在任意需要的地方都可以获取,在不同的线程中,通过同一个ThreadLocal获取到不同的对象。

其原理如图:

ThreadLocal的实现原理:在每个线程中使用ThreadLocalMap将键值对<ThreadLocal,Object>保存在使用线性探测法实现的hash表中(HashMap是链接法实现的hash表)。实现代码不做具体阐述。

2.ThreadLocal内存泄漏

ThreadLocalMap中,Entry继承WeakReference<ThreadLocal>,并且Entry中没有保存key而是使用WeakReference中的成员referent保存ThreadLocal,作为Entry的key。

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

因此,当初始化ThreadLocal的外部强引用被清空后,Entry中的referent将会在下次JVM垃圾回收时被回收。因而ThreadLocalMap中将出现一个key为null的Entry。这些null key存在一条强引用链的关系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,这条强引用链会导致Entry不会回收,Value也不会回收,但Entry中的Key却已经被回收的情况,造成内存泄漏。

但是JVM团队已经考虑到这样的情况,并做了一些措施来保证ThreadLocal尽量不会内存泄漏:在ThreadLocal的get()、set()、remove()方法调用的时候尝试清除掉线程ThreadLocalMap中部分Entry中Key为null的Value,并将整个Entry设置为null,利于下次内存回收。

public class ThreadLocalTest {
	
	public static void main(String[] args) throws Exception {
		threadLocalMemoryLeakTest();
	}

	public static void threadLocalMemoryLeakTest() throws Exception {
		ThreadLocal<String> threadLocal = new ThreadLocal<>();
		threadLocal.set("hello Thread local");
		System.out.printf("%s:%s%n", threadLocal, threadLocal.get());
		
		System.out.println("------------------begin------------------");
		// 反射获取实现ThreadLocalMap的Hash表: Entry[]
		Field field = Thread.class.getDeclaredField("threadLocals");
		field.setAccessible(true);
		Object threadLocalMap = field.get(Thread.currentThread());
		Field entryTableField = threadLocalMap.getClass().getDeclaredField("table");
		entryTableField.setAccessible(true);
		Object table = entryTableField.get(threadLocalMap);
		
		printEntryTable(table);
		// 清除外部强引用
		threadLocal = null;
		// 触发垃圾回收
		System.gc();
		System.out.println("--------------------gc--------------------");
		// 打印Hash表
		printEntryTable(table);
	}

	private static void printEntryTable(Object table) throws NoSuchFieldException, IllegalAccessException {
		if (table.getClass().isArray()) {
			int length = Array.getLength(table);
			Class<?> entryClass = table.getClass().getComponentType();
			Class<?> referenceClass = entryClass.getSuperclass().getSuperclass();
			Field keyField = referenceClass.getDeclaredField("referent");
			Field valueField = entryClass.getDeclaredField("value");
			keyField.setAccessible(true);
			valueField.setAccessible(true);
			
			for (int slot = 0; slot < length; slot++) {
				Object entry = Array.get(table, slot);
				if (entry == null) {
					continue;
				}
				
				Object key = keyField.get(entry);
				Object value = valueField.get(entry);
				System.out.printf("[%2d]%s:%s%n", slot, key, value);
			}
		}
	}
}

打印结果如下:

java.lang.ThreadLocal@28d93b30:hello Thread local
------------------begin------------------
[ 3]java.lang.ThreadLocal@28d93b30:hello Thread local
[ 5]java.lang.ThreadLocal@677327b6:[Ljava.lang.Object;@14ae5a5
[ 7]java.lang.ThreadLocal@7f31245a:java.lang.ref.SoftReference@6d6f6e28
[14]java.lang.ThreadLocal@135fbaa4:java.lang.ref.SoftReference@45ee12a7
--------------------gc--------------------
[ 3]null:hello Thread local
[ 5]java.lang.ThreadLocal@677327b6:[Ljava.lang.Object;@14ae5a5
[ 7]java.lang.ThreadLocal@7f31245a:java.lang.ref.SoftReference@6d6f6e28
[14]java.lang.ThreadLocal@135fbaa4:java.lang.ref.SoftReference@45ee12a7

通过对比对象地址,测试方法中新加的Threadlocal位于ThreadlocalMap中下标为3的槽位上,gc前Entry的key为java.lang.ThreadLocal@28d93b30,gc后变成null说明已经被垃圾回收了,但是Entry中的value任然存在,也就是存在内存泄漏了。

接下来,我们在上面的测试代码中加入以下部分测试代码:

        System.out.println("-------------------set------------------");
        threadLocal = new ThreadLocal<>();
        threadLocal.set("hello java");
        System.out.printf("%s:%s%n", threadLocal, threadLocal.get());

输出如下:

-------------------set------------------
java.lang.ThreadLocal@330bedb4:hello java
[ 3]null:hello Thread local
[ 5]java.lang.ThreadLocal@677327b6:[Ljava.lang.Object;@14ae5a5
[ 7]java.lang.ThreadLocal@7f31245a:java.lang.ref.SoftReference@6d6f6e28
[10]java.lang.ThreadLocal@330bedb4:hello java
[14]java.lang.ThreadLocal@135fbaa4:java.lang.ref.SoftReference@45ee12a7

新加入的ThreadLocalMap插入到了下标为10的位置了,并且之前垃圾回收的key为null的Entry仍然存在,并没有被清理掉。难道是ThreadLocal中清理失效的Entry的机制没生效吗?我们来看看代码:

        private void set(ThreadLocal<?> key, Object value) {
            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)]) {
                ThreadLocal<?> k = e.get();

                if (k == key) {
                    e.value = value;
                    return;
                }

                if (k == null) {
                    replaceStaleEntry(key, value, i);
                    return;
                }
            }

            tab[i] = new Entry(key, value);
            int sz = ++size;
            // 尝试清理some槽位
            if (!cleanSomeSlots(i, sz) && sz >= threshold)
                rehash();
        }
        
        private boolean cleanSomeSlots(int i, int n) {
            boolean removed = false;
            Entry[] tab = table;
            int len = tab.length;
            do {
                i = nextIndex(i, len);
                Entry e = tab[i];
                if (e != null && e.get() == null) {
                    n = len;
                    removed = true;
                    i = expungeStaleEntry(i);
                }
            } while ( (n >>>= 1) != 0);
            return removed;
        }

很显然,ThreadLocalMap的set方法中,最后只是清理一部分槽位,并没有全部清理。检测的槽位数量为\left \lfloor \log_2(size) \right \rfloor + 1我们可以多插入几个对象试试。

        System.out.println("-------------------set------------------");
        for (int i = 0; i < 2; i++) {
            threadLocal = new ThreadLocal<>();
            threadLocal.set("hello java_" + i);
        }
        printEntryTable(table);
-------------------set------------------
[ 1]java.lang.ThreadLocal@330bedb4:hello java_1
[ 5]java.lang.ThreadLocal@677327b6:[Ljava.lang.Object;@14ae5a5
[ 7]java.lang.ThreadLocal@7f31245a:java.lang.ref.SoftReference@6d6f6e28
[10]java.lang.ThreadLocal@2503dbd3:hello java_0
[14]java.lang.ThreadLocal@135fbaa4:java.lang.ref.SoftReference@45ee12a7

插入第二个对象时,失效的Entry被清理掉了。

3.ThreadLocal最佳实践

    private static ThreadLocal<String> threadLocal = new ThreadLocal<>();
	
    public void func() {
        threadLocal.set("hello");
        try {
            // do something
        } finally {
            threadLocal.remove();
        }
    }

1.Threadlocal成员变量推荐设置为静态变量static。假如设置为非静态变量,如果ThreadLocal所在的类实例了多个对象,那么同一个线程中该对象可能会存储不同的值,也就是说存储的值只在对象内部有效。

2.Threadlocal对象不再后,调用remove()回收掉,防止内存泄漏。

4.FastThreadLocal原理

Netty框架中自己实现了一个等价于ThreadLocal的类,即FastThreadLocal。顾名思义,就是使用的时候速度快,效率高。

其基本用法和ThreadLocal一样:

    FastThreadLocal<String> ftl = new FastThreadLocal<>();
    ftl.set("hello FastThreadLocal");
    System.out.println(ftl.get());
    private static final int variablesToRemoveIndex = InternalThreadLocalMap.nextVariableIndex();

    private final int index;    

    public FastThreadLocal() {
        index = InternalThreadLocalMap.nextVariableIndex();
    }
    public static int nextVariableIndex() {
        int index = nextIndex.getAndIncrement();
        if (index < 0) {
            nextIndex.decrementAndGet();
            throw new IllegalStateException("too many thread-local indexed variables");
        }
        return index;
    }

FastThreadLocal构造函数中定义了一个final修饰的index,而且这个index是按序号递增的。下面我们来看一下set和get方法,通过这两个方法了解Index的作用。

    public final void set(V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            set(InternalThreadLocalMap.get(), value);
        } else {
            remove();
        }
    }

    public final void set(InternalThreadLocalMap threadLocalMap, V value) {
        if (value != InternalThreadLocalMap.UNSET) {
            if (threadLocalMap.setIndexedVariable(index, value)) {
                addToVariablesToRemove(threadLocalMap, this);
            }
        } else {
            remove(threadLocalMap);
        }
    }
    public boolean setIndexedVariable(int index, Object value) {
        Object[] lookup = indexedVariables;
        if (index < lookup.length) {
            Object oldValue = lookup[index];
            lookup[index] = value;
            return oldValue == UNSET;
        } else {
            expandIndexedVariableTableAndSet(index, value);
            return true;
        }
    }

set()方法中,首先判断插入的值是不是默认值,如果是默认值,默认为一个删除操作。如果不是,则做插入操作。最终set操作是由InternalThreadLocalMap实现的:以index作为下标,替换indexedVariables数组中的对象;如果下标越界,则先扩容然后再插入数组。

从此处我们可以看出,FastThreadLocal使用Object[]作为容器,初始化的时候初始化一个index作为下标存储FastThreadLocal对应的Object。以“数组+下标”的替代线性探测法的Hash表实现,去掉hash、线性探测以及定位槽位的过程,从而提升性能。

除了实现方法,还有两个细节值得注意:

  1. set()方法中,通过InternalThreadLocalMap.get()获取InternalThreadLocalMap对象。
  2. set()插入value后,FastThreadLocal调用了addToVariablesToRemove()方法。

下面分别看看这两个方法有什么特殊之处。

  • 首先看InternalThreadLocalMap.get()方法:
    public static InternalThreadLocalMap get() {
        Thread thread = Thread.currentThread();
        if (thread instanceof FastThreadLocalThread) {
            return fastGet((FastThreadLocalThread) thread);
        } else {
            return slowGet();
        }
    }

    private static InternalThreadLocalMap fastGet(FastThreadLocalThread thread) {
        InternalThreadLocalMap threadLocalMap = thread.threadLocalMap();
        if (threadLocalMap == null) {
            thread.setThreadLocalMap(threadLocalMap = new InternalThreadLocalMap());
        }
        return threadLocalMap;
    }

    private static InternalThreadLocalMap slowGet() {
        ThreadLocal<InternalThreadLocalMap> slowThreadLocalMap = UnpaddedInternalThreadLocalMap.slowThreadLocalMap;
        InternalThreadLocalMap ret = slowThreadLocalMap.get();
        if (ret == null) {
            ret = new InternalThreadLocalMap();
            slowThreadLocalMap.set(ret);
        }
        return ret;
    }

第一步首先判断当前线程是否为FastThreadLocalThread,如果是调用fastGet(),否则调用slowGet()。而FastThreadLocalThread中只包含一个InternalThreadLocalMap对象。

public class FastThreadLocalThread extends Thread {

    private InternalThreadLocalMap threadLocalMap;

}

从前面的fastGet()和slowGet()方法中,可以看出来这两个方法的区别在于InternalThreadLocalMap的持有对象不同:FastThreadLocalThread中持有InternalThreadLocalMap对象,所以fastGet()直接从FastThreadLocalThread对象中获取;而一般线程没有持有InternalThreadLocalMap对象,所以是保存在当前线程的ThreadLocalMap中。而在ThreadLocalMap中保存InternalThreadLocalMap对象,并没有去除JDK的ThreadLocal所存在的问题,相反会使FastThreadLocal的实现更复杂,从而效率比ThreadLocal更低。所以Netty中的DefaultThreadFactory的newThread方法返回的都是FastThreadLocalThread。

  • FastThreadLocal在set()插入value后,调用了addToVariablesToRemove()方法。我们看看它的实现:
    private static void addToVariablesToRemove(InternalThreadLocalMap threadLocalMap, FastThreadLocal<?> variable) {
        Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
        Set<FastThreadLocal<?>> variablesToRemove;
        if (v == InternalThreadLocalMap.UNSET || v == null) {
            variablesToRemove = Collections.newSetFromMap(new IdentityHashMap<FastThreadLocal<?>, Boolean>());
            threadLocalMap.setIndexedVariable(variablesToRemoveIndex, variablesToRemove);
        } else {
            variablesToRemove = (Set<FastThreadLocal<?>>) v;
        }

        variablesToRemove.add(variable);
    }

threadLocalMap从下标variablesToRemoveIndex(FastThreadLocal的静态变量,variablesToRemoveIndex值为0)获取一个对象,如果对象是null,就生成一个从IdentityHashMap转化的Set对象,用于保存所有threadLocalMap中的对象。

variablesToRemoveIndex下标中保存的threadLocalMap中所有插入的对象,在removeAll方法中即用此遍历清空所有对象。

public static void removeAll() {
        InternalThreadLocalMap threadLocalMap = InternalThreadLocalMap.getIfSet();
        if (threadLocalMap == null) {
            return;
        }

        try {
            Object v = threadLocalMap.indexedVariable(variablesToRemoveIndex);
            if (v != null && v != InternalThreadLocalMap.UNSET) {
                @SuppressWarnings("unchecked")
                Set<FastThreadLocal<?>> variablesToRemove = (Set<FastThreadLocal<?>>) v;
                FastThreadLocal<?>[] variablesToRemoveArray =
                        variablesToRemove.toArray(new FastThreadLocal[variablesToRemove.size()]);
                for (FastThreadLocal<?> tlv: variablesToRemoveArray) {
                    tlv.remove(threadLocalMap);
                }
            }
        } finally {
            InternalThreadLocalMap.remove();
        }
    }

此处的variablesToRemoveIndex所维护的Set保存所有FastThreadLocal的操作,一开始不太理解这个机制的意图。按理说只用于遍历的话,使用threadLocalMap清空数组就完事了,为什么要在这里维护一个Set<FastThreadLocal>的容器呢?思考一翻后,应该只有一个解释了:事件监听

    public final void remove(InternalThreadLocalMap threadLocalMap) {
        if (threadLocalMap == null) {
            return;
        }

        Object v = threadLocalMap.removeIndexedVariable(index);
        removeFromVariablesToRemove(threadLocalMap, this);

        if (v != InternalThreadLocalMap.UNSET) {
            try {
                onRemoval((V) v);
            } catch (Exception e) {
                PlatformDependent.throwException(e);
            }
        }
    }

当FastThreadLocal被删除时,会调用一个onRemoval()方法,其中内存池就用到了这个事件监听机制,用于释放线程缓存数据的操作。

    final class PoolThreadLocalCache extends FastThreadLocal<PoolThreadCache> {

        @Override
        protected void onRemoval(PoolThreadCache threadCache) {
            threadCache.free();
        }
        // ...
    }

5.FastThreadLocal最佳实践

前面说过了,使用FastThreadLocal时,必须使用FastThreadLocalThread线程。

        Thread[] threads = new Thread[4];
        DefaultThreadFactory f = new DefaultThreadFactory("FastThreadLocalThread-");
        for (int i = 0; i < threads.length; i++) {
            threads[i] = f.newThread(() -> {
                // do something
            });
        }
		
        for (Thread thread : threads) {
            thread.start();
        }

DefaultThreadFactory.newThread()方法创建的任务会将Runnable包装为DefaultRunnableDecorator,在任务执行完之后,会移除所有的FastThreadLocal对象。

    private static final class DefaultRunnableDecorator implements Runnable {

        private final Runnable r;

        DefaultRunnableDecorator(Runnable r) {
            this.r = r;
        }

        @Override
        public void run() {
            try {
                r.run();
            } finally {
                FastThreadLocal.removeAll();
            }
        }
    }

6.ThreadLocal与FastThreadLocal性能比较

下面给出一组测试,测试ThreadLocal与FastThreadLocal的读取性能:

public class ThreadLocalTest {
	
	public static void main(String[] args) throws Exception {
		threadLocalTest();
		fastThreadLocalTest();
	}
	
	private static int THREAD_NUM = 2;
	
	public static void fastThreadLocalTest() throws InterruptedException {
		Thread[] threads = new Thread[THREAD_NUM];
		 DefaultThreadFactory f = new DefaultThreadFactory("FastThreadLocal-");
		for (int i = 0; i < threads.length; i++) {
			threads[i] = f.newThread(() -> {
				FastThreadLocal<Long> ftl = new FastThreadLocal<>();
				ftl.set(1L);
				long sum = 0;
				long start = System.nanoTime();
				for (int j = 0; j < 1000000000; j++) {
					sum = ftl.get();
				}
				ftl.remove();
				long end = System.nanoTime();
				System.out.printf("[%20s] sum:%s, cost time:%s ns%n", Thread.currentThread().getName(), sum, (end - start));
			});
		}
		
		for (Thread thread : threads) {
			thread.start();
		}
		for (Thread thread : threads) {
			thread.join();
		}
	}
	
	public static void threadLocalTest() throws InterruptedException {
		Thread[] threads = new Thread[THREAD_NUM];
		for (int i = 0; i < threads.length; i++) {
			threads[i] = new Thread(() -> {
				ThreadLocal<Long> tl = new ThreadLocal<>();
				tl.set(1L);
				long sum = 0;
				long start = System.nanoTime();
				for (int j = 0; j < 1000000000; j++) {
					sum = tl.get();
				}
				tl.remove();
				long end = System.nanoTime();
				System.out.printf("[%20s] sum:%s, cost time:%s ns%n", Thread.currentThread().getName(), sum, (end - start));
			}, "ThreadLocalThread-" + (i + 1));
		}
		
		for (Thread thread : threads) {
			thread.start();
		}
		for (Thread thread : threads) {
			thread.join();
		}
	}
}

输出:

[FastThreadLocal--1-1] sum:1, cost time:20086153 ns
[FastThreadLocal--1-2] sum:1, cost time:33817537 ns
[ ThreadLocalThread-2] sum:1, cost time:5866945917 ns
[ ThreadLocalThread-1] sum:1, cost time:5866982702 ns

FastThreadLocal的get性能比ThreadLocal的读性能提高两个数量级。

将上面循环读取的代码,改成如下测试写性能:

            for (int j = 0; j < 1000000000; j++) {
                tl.set((long)j);
            }
            sum = tl.get();
[FastThreadLocal--1-1] sum:999999999, cost time:10769466909 ns
[FastThreadLocal--1-2] sum:999999999, cost time:10783395903 ns
[ ThreadLocalThread-1] sum:999999999, cost time:22287503598 ns
[ ThreadLocalThread-2] sum:999999999, cost time:22293425934 ns

FastThreadLocal的set性能比ThreadLocal的读性能提高一倍。

 

7.总结

  1. ThreadLocal将自己作为key,设置的对象作为value插入Thread.ThreadLocalMap中。
  2. 每个线程get/set都是从自己的ThreadLocalMap成员变量中读写,从而保证数据线程隔离。
  3. 当ThreadLocal的外部强引用全部设置为null时,JVM触发垃圾回收后,会回收掉ThreadLocal的键值对Entry中的key,使其为空,从而造成内存泄漏。
  4. 当ThreadLocalMap中存在get,set或remove操作时,会触发检测一部分槽位,清理key为null的Entry。
  5. FastThreadLocal初始化时按序生成index,作为存储、访问数组实现的InternalThreadLocalMap的下标,实现快速读写访问。
  6. 使用FastThreadLocal时推荐使用FastThreadLocalThread作为线程,否则无法提高读写访问速度,甚至效率会降低。
  7. FastThreadLocal的get性能较ThreadLocal提高两个数量级,而set性能比ThreadLocal的读性能提高一倍左右。

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: ThreadLocal 是 Java 中的一个类,可以为每个线程绑定一个特定的变量,这样每个线程就可以访问自己绑定的变量了。使用时有一些注意点: 1. 不要将 ThreadLocal 的实例定义为 static 变量,因为这样会导致所有线程共享同一个变量,而不是每个线程都有自己的变量。 2. 在线程使用完 ThreadLocal 变量之后,一定要记得调用 remove() 方法清除线程的变量副本,因为如果不清除,线程池回收线程时,ThreadLocal 实例变量可能无法被回收,导致内存泄漏。 3. 在使用 ThreadLocal 的过程中,一定要注意线程安全问题。 4. 如果使用的是继承自 ThreadLocal 的匿名内部类,要注意内存泄漏的问题。 希望这些信息对你有帮助。 ### 回答2: ThreadLocal 是一个用于线程私有变量的类。它可以在多线程环境下确保每个线程都拥有独立的变量副本,避免了线程之间的数据共享和竞争。 使用 ThreadLocal 需要注意以下几点: 1. 避免内存泄漏:使用 ThreadLocal 时,要注意及时清理线程中的变量副本,避免长时间持有变量导致内存泄漏。 2. 初始值的设置:ThreadLocal 变量需要设置初始值,确保每个线程首次访问变量时能够获得正确的初始值。可以通过重写 ThreadLocal 的 initialValue() 方法来设置初始值。 3. 线程复用的问题:在线程池等线程复用的场景下,要注意 ThreadLocal 变量在线程复用时可能会引起数据污染的问题,需要在每次使用 ThreadLocal 变量之前,通过 remove() 方法将变量副本清除,保证线程复用时变量的独立性。 4. 变量的线程安全性:尽管 ThreadLocal 可以保证每个线程有独立的变量副本,但是,如果被存储在 ThreadLocal 中的变量本身不是线程安全的,仍然可能出现线程安全问题。因此,要注意保证存储在 ThreadLocal 中的变量的线程安全性。 5. 变量的传递问题:由于 ThreadLocal 变量只能在当前线程内共享,因此在不同线程之间传递数据需要通过其他方式,例如,可以利用线程池的 ThreadLocalMap 来实现传递。 总之,使用 ThreadLocal 时,要注意清理变量副本、设置初始值、处理线程复用、保证变量的线程安全性以及解决变量传递问题,以确保正常使用并避免潜在的问题。 ### 回答3: ThreadLocal 是一个 Java 中的线程局部变量,它提供了线程内的共享变量,在多线程环境下可以保证每个线程都拥有自己独立的变量副本,互不干扰。在使用 ThreadLocal 时需要注意以下几点: 1. 内存泄漏问题:使用 ThreadLocal 时需要小心内存泄漏问题。由于 ThreadLocal 中的变量是每个线程独立的,如果没有及时清理 ThreadLocal 对应的变量,可能会导致长时间不使用的线程仍然存在于内存中,造成内存泄漏。因此,在使用完 ThreadLocal 后应该显式地调用 remove() 方法清理对应的变量。 2. 初始化问题:ThreadLocal 变量的初始化是在每个线程中进行的,因此每个线程都会有一个对应的初始化值。在使用 ThreadLocal 时需要关注初始化值是否满足业务需求,否则可能会导致错误的结果。 3. 无法共享数据:虽然 ThreadLocal 在每个线程中都可以独立使用变量,但是无法实现线程间的数据共享。如果需要线程间的数据传递或共享,应该使用其他方式,如使用共享变量或传递参数等。 4. 线程重用问题:在线程重用的场景中,例如线程池,由于线程对象被复用,ThreadLocal 中的变量可能会被上一次使用的线程遗留下来,导致出现错误的结果。因此,在使用线程池等重用线程的情况下,需要特别小心 ThreadLocal 的使用。 总之,ThreadLocal 是一个非常有用的工具,能够解决线程间的变量共享问题。但是在使用时需要注意内存泄漏问题、初始化问题、无法共享数据和线程重用问题,以确保程序的正确性和性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值