ThreadLocal那点事

2023-05-16

目录

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的读性能提高一倍左右。

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ThreadLocal那点事 的相关文章

  • ThreadLocal类

    ThreadLocal类 什么是ThreadLocal为什么ThreadLocal是线程安全的呢 什么是ThreadLocal ThreadLocal可以简单的理解为他其实就是一个工具类 xff0c 用来存储线程局部变量的一个工具类 xff
  • ThreadLocal 适合用在哪些实际生产的场景中?

    在通常的业务开发中 xff0c ThreadLocal有两种典型的使用场景 场景1 xff0c ThreadLocal 用作保存每个线程独享的对象 xff0c 为每个线程都创建一个副本 xff0c 这样每个线程都可以修改自己所拥有的副本 而
  • ThreadLocal的深度解读

    一 J2SE的原始描述 This class provides thread local variables These variables differ from their normal counterparts in that eac
  • 深入理解ThreadLocal源码

    1 预备知识 强软弱虚引用 在Java中有四种引用的类型 强引用 软引用 弱引用 虚引用 设计这四种引用的目的是可以用程序员通过代码的方式来决定对象的生命周期 方便GC 强引用 强引用是程序代码中最广泛使用的引用 如下 Object o n
  • ThreadLocal,看我就够了!

    ThreadLocal 开胃菜 研究过Handler的应该对ThreadLocal比较眼熟的 线程中的Handler对象就是通过ThreadLocal来存放的 初识ThreadLocal的可能被它的名字有所误导 ThreadLocal初一看
  • ThreadLocal和ThreadLocalMap

    1 ThreadLocal是什么 是用来存放我们需要能够线程隔离的变量的 那就是线程本地变量 也就是说 当我们把变量保存在ThreadLocal当中时 就能够实现这个变量的线程隔离了 entry中的key使用了弱引用 static clas
  • 一篇文章,从源码深入详解ThreadLocal内存泄漏问题

    原创文章 经验总结 从校招到A厂一路阳光一路沧桑 详情请戳www coderccc com 1 造成内存泄漏的原因 threadLocal是为了解决对象不能被多线程共享访问的问题 通过threadLocal set方法将对象实例保存在每个线
  • SimpleDateFormat线程不安全及解决办法

    以前没有注意到SimpleDateFormat线程不安全的问题 写时间工具类 一般写成静态的成员变量 不知 此种写法的危险性 在此讨论一下SimpleDateFormat线程不安全问题 以及解决方法 为什么SimpleDateFormat不
  • ThreadLocal 和内存泄漏

    在多个帖子中都提到 不当使用ThreadLocal导致内存泄漏 我正在努力理解内存泄漏是如何发生的ThreadLocal 我想到的唯一场景如下 Web 服务器维护一个线程池 例如 用于 servlet 如果变量在这些线程中 则可能会造成内存
  • ThreadLocal - 用作带有 spring-boot 的 REST API 的上下文信息

    我有一些spring boot应用程序 它公开了 REST API 提到的 REST API 是由spring security 一切都很好 但是现在我需要设置上下文 用于服务请求 设置上下文是指根据用户上下文选择数据源 关键是Routin
  • 使用 ThreadLocal 作为数据上下文是个好主意吗?

    使用 ThreadLocal 作为 Web 应用程序中数据的上下文是个好主意吗 这就是它的目的 但请注意删除上下文末尾的 ThreadLocal 否则可能会出现内存泄漏 或者至少会保留未使用的数据太长时间 ThreadLocals 也非常快
  • 扩展 java 的 ThreadLocal 以允许在所有线程中重置值

    看完之后这个问题 https stackoverflow com questions 2795447 is there no way to iterate over or copy all the values of a java thre
  • 为什么 Java 语言设计者对于大多数基于散列的结构(除了 ThreadLocal 之类的结构之外)更喜欢使用链接而不是开放寻址? [关闭]

    很难说出这里问的是什么 这个问题是含糊的 模糊的 不完整的 过于宽泛的或修辞性的 无法以目前的形式得到合理的回答 如需帮助澄清此问题以便重新打开 访问帮助中心 help reopen questions 我知道解决哈希冲突的开放寻址和链接之
  • Spring安全策略MODE_INHERITABLETHREADLOCAL。为什么?

    我了解当我们在 Spring 安全策略中使用 MODE THREADLOCAL 和 MODE INHERITABLETHREADLOCAL 时如何以及会发生什么 我不明白的是 为什么有人会使用 MODE THREADLOCAL 而不是 MO
  • Java 的 ThreadLocal 底层是如何实现的?

    ThreadLocal是如何实现的 它是用 Java 实现的 使用一些从 ThreadID 到对象的并发映射 还是使用一些 JVM 钩子来更有效地完成它 这里的所有答案都是正确的 但有点令人失望 因为它们在某种程度上掩盖了如何聪明Threa
  • 在实例变量中使用 ThreadLocal

    Do Java ThreadLocal如果变量用作实例变量 则它们会生成线程局部值 例如 在生成线程本地对象的方法中 或者它们必须始终是静态的吗 作为一个例子 假设一个典型的场景 其中几个初始化非线程安全类的对象的成本很高 需要在单个静态初
  • ThreadLocal 和 SimpleDateFormat 数组

    使用与中描述的模式非常相似的模式最近的问题 https stackoverflow com questions 10491135 threadlocal for multithreaded access to simpledateforma
  • 将 ThreadLocal 传播到从 ExecutorService 获取的新线程

    我正在一个单独的线程中运行一个带有超时的进程 使用 ExecutorService 和 Future 示例代码here https stackoverflow com questions 1164301 how do i call some
  • 为什么 ThreadLocal 实用程序在 Spring MVC 应用程序中总是返回 null?

    我编写了这个实用程序类来在 Spring MVC 应用程序中保存临时数据 public abstract class FooUtil private static final ThreadLocal
  • Python 中上下文相关的日志级别

    我正在用 Python 制作一个 Web 应用程序框架的原型 主要是为了教育目的 但我一直坚持一个我一直想要的功能 每条路由的日志级别 此功能的目标是识别我们正在执行诊断的一些特定入口点 例如 我想跟踪呼叫者拨打电话时发生的情况POST s

随机推荐

  • 员工管理系统(C 语言)——服务器解析

    源码下载地址 xff1a https download csdn net download wenfei11471 10477504 服务器功能 xff1a 1 运行时主界面 xff08 服务器启动后 xff0c 只有管理员下线 xff0c
  • 排序——选择排序、冒泡排序和快速排序比较

    一 选择排序思路 xff1a 1 以 int 类型为例 2 拿第一个数与后面数相比较 xff0c 如果比后面的数大则交换 3 拿第二个数与后面的数比较 xff0c 如果比后面的数大则交换 4 直到比较到倒数第二个数 xff0c 最后一个数不
  • C 语言中 const 与指针的结合使用

    请区分一下几种指针的区别 1 const int p 2 int const p 3 int const p 4 const int const p 5 const int const p 解析 xff1a 1 const int p 中
  • Ubuntu16.04上安装百度网盘后打不开

    现在百度网盘推出了Linux版本 xff0c 也有Ubuntu下安装的deb文件 xff0c 但是我在Ubuntu上安装后却打不开 xff0c 报错 baidunetdisk crashed with SIGABRT in gnu cxx
  • C/C++的“文件包含”处理时头文件被重复包含的问题探究及解决方法(用最简单的例子进行说明)

    这篇博文是博文https blog csdn net wenhao ir article details 125668051的配套博文 头文件被重复包含是下面这样的现象 xff1a A文件里包含了C文件 xff0c B文件里也包含了C文件
  • BIN,BCD,ASCII码分别对应的Hex(16进制)数

    BIN BCD ASCII码分别对应的Hex xff08 16进制 xff09 数 以十进制的 56 为例 BIN 码 对应二进制数为 0011 1000对应Hex数据为 0x38BIN码就是二进制数 xff1b 压缩BCD 码 对应二进制
  • .LDS 文件详解

    最近在研究uboot xff0c 红色部分为我加上的注解 转载地址 xff1a http blog chinaunix net space php uid 61 23373524 amp do 61 blog amp cuid 61 232
  • 13 select的优化一

    1 上个例子中 xff0c select通过for循环轮询client套接字 xff0c 轮询的范围比较大 xff0c 有优化的地方 2 优化代码 xff1a 通过数组存储client的套接字 xff0c 达到少轮询的效果 xff0c 可以
  • 二.手写迷你版Tomcat-minicat2.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0需求 xff1a 封装Request和Response对象 xff0c 返回html静态资源文件 封装Request对象 想要封
  • 三.手写迷你版Tomcat-minicat3.0

    minicat 1 0我们实现了返回固定的字符串 34 Hello minicat 34 minicat 2 0封装Request和Response对象 xff0c 返回html静态资源文件 minicat 3 0需求 xff1a 请求se
  • python爬取全国五级行政区

    以前爬过国家统计局的四级行政区 xff08 http www stats gov cn tjsj tjbz tjyqhdmhcxhfdm 2017 xff09 xff0c 但是对于五级数据效果不是很好 偶然间发现这个网站 xff1a htt
  • ElasticSearch使用elasticsearchTemplate聚合查询

    这两天正好做个需求 xff0c 需要用到聚合查询 前几篇文章只是简单的提到过 xff0c 并没有真正的运用到实际产出中 xff0c 本篇结合实际代码 xff0c 专项学习ES的聚合查询 1 业务背景 有一张地址索引表 xff1a hisAd
  • Java字节码

    Java最黑科技的玩法就是字节码编程 xff0c 也就是动态修改或是动态生成 Java 字节码 使用字节码可以玩出很多高级的玩法 xff0c 最高级的还是在 Java 程序运行时进行字节码修改和代码注入 听起来是不是一些很黑客 xff0c
  • TCP/IP (一) accept建立连接

    七层网络协议 三次握手 四次分手 xff0c 这些大家都比较熟知 xff0c 这里主要是带着一些问题来思考整个TCP IP流程 1 三次握手的具体流程是怎么样的 xff1f 2 socket编程中int listen int fd int
  • http 的认证模式

    周海汉 2006 7 11 ablozhou 64 gmail com SIP类似Http协议 其认证模式也一样 Http协议 xff08 RFC 2616 xff09 规定可以采用Base模式和摘要模式 xff08 Digest sche
  • Java Agent

    在 Java 字节码 一文中有提到 xff0c 使用 Java Agent 操控字节码 xff0c 本文将讨论 Java Agent xff0c 这是普通 Java 开发人员的真正的黑魔法 Java Agent 能够通过执行字节码的直接修改
  • 通过gitlab远程统计git代码量

    git的代码量大多数都是根据命令行统计 xff0c 或者根据第三方插件统计 但是都不满足我的需求 xff0c 因为我们代码都由gitlab管理 xff0c 于是想到了通过gitlab暴露出来的接口获取数据 第一步 xff0c 生成私钥 登录
  • Qt第二十二章:将控件放到另一个控件的后面或前面

    话不多说 xff1a 看图
  • 缓存行填充与@sun.misc.Contended注解

    1 缓存模型 CPU和主内存之间有好几层缓存 xff0c 因为与cpu的速度相比 xff0c 访问主内存的速度是非常慢的 如果频繁对同一个数据做运算 xff0c 每次都从内存中加载 xff0c 运算完之后再写回到主内存中 xff0c 将会严
  • ThreadLocal那点事

    目录 1 ThreadLocal原理 2 ThreadLocal内存泄漏 3 ThreadLocal最佳实践 4 FastThreadLocal原理 5 FastThreadLocal最佳实践 6 ThreadLocal与FastThrea