大家一说到关于c thread传递参数或者和std::thread传递参数的话题,总是能引起大家关注度,小编为你带来解。
什么是ThreadLocal
ThreadLocal类,顾名思义,可以理解为线程局部变量。也就是说,如果定义了一个ThreadLocal,那么每个线程对这个ThreadLocal的读写都是线程隔离的,不会互相影响。它提供了一种通过传递可变数据来实现线程关闭的机制,每个线程都有自己独立的副本。
实际应用
在实际开发中,我们真正使用ThreadLocal的场景还是比较少的,大部分都是在框架中使用。最常见的使用场景是用它来解决数据库连接、Session管理等,保证每个线程使用的数据库连接是相同的。另外一个用的比较多的场景是通过SimpleDateFormat来解决线程不安全的题,不过现在java8提供了DateTimeFormatter是线程安全的,有兴趣的同学可以去看看。它还可以用来优雅地传递参数。传递参数时,如果将父线程生成的变量或参数直接通过ThreadLocal传递给子线程,参数将会丢失。这个后面会介绍另一个ThreadLocal来专门解决这个题。
ThreadLocalAPI简介
ThreadLocal的API还是比较少,就几个API
我们来看看这些API的使用,使用起来超级简单
私有静态ThreadLocallt;Stringgt;threadLocal=ThreadLocalwithInitial->34;publicstaticvoidmainString[]args输出结果
获取初始值Java财经获取修改值关注【Java财经】炒鸡是不是很简单,几行代码就覆盖了所有api。我们简单看一下这些API的源码。
成员变量
/初始容量,必须是2的幂初始容量--必须是2的幂/privatestaticfinalintINITIAL_CAPACITY=16;/条目表,大小必须是2的幂该表,根据需要调整大小tablelength必须始终是2的幂/privateEntry[]表;/表中的条目数/privateintsize=0;/调整大小的下一个大小值/privateintThreshold;//默认为0这里经常会有一个面试到的题是为什么入口数组的大小和初始容量必须是2的幂?对于firstKeythreadLocalHashCodeINITIAL_CAPACITY-1;而且很多源代码都使用hashCode而不是hashCode。这种写法的优点如下
使用位运算代替模运算,以提高计算效率。
为了使不同哈希值之间碰撞的概率更小,元素在哈希表中尽可能均匀地进行哈希处理。
设置方法
publicvoidsetT值设置方法比较简单。我们可以重点关注这个方法中的ThreadLocalMap。既然是map,就必须有自己的key和value。根据源码我们可以看到,它的key其实是可以设置的,简单的看成是ThreadLocal,但实际上ThreadLocal存储的是ThreadLocal的弱引用,它的值就是我们实际设置的值
静态类Entry扩展WeakReferencelt;ThreadLocallt;gt;gt;Entry是ThreadLocalMap中定义的节点,它继承了WeakReference类,定义了一个Object类型的值,用于存储填充到ThreadLocal中的值。我们看一下这个ThreadLocalMap位于哪里?我们看到ThreadLocalMap是一个位于Thread中的变量,我们的值放在ThreadLocalMap中,这样就可以实现各个线程之间的隔离。下面两张图基本把ThreadLocal的结构介绍清楚了。
接下来我们看一下ThreadLocalMap中的数据结构。我们知道HaseMap通过链表和红黑树来解决哈希冲突。但是我们看到ThreadLocalMap只有一个数组。它如何解决哈希冲突?ThreadLocalMap采用“线性检测”的方法。什么是线性检测?就是根据初始key的hashcode值来确定元素在table数组中的位置。如果发现该位置已经有另一个键值的元素被占用,则使用固定算法以一定的步长找到下一个位置,依次判断。直到找到可以存放的地方。”ThreadLocalMap解决Hash冲突的方式就是简单的在步长上加或减1来找到下一个相邻的位置。
/自增imodulolen/privatestaticintnextIndexinti,intlen/自减imodulolen/privatestaticintprevIndexinti,intlen这样,如果一个线程中有大量的ThreadLocal,就会有是性能题,因为每次都需要遍历这张表来清除无效值。因此,我们使用尽可能少的ThreadLocal,并且不要在线程中创建大量的ThreadLocal。如果我们需要设置不同的参数类型,我们可以使用ThreadLocal来存储一个Object的Map。这样就可以大大减少创建的ThreadLocal的数量。伪代码如下
公共最终类HttpContext私有静态最终ThreadLocallt;Maplt;String,Objectgt;gt;CONTEXT=ThreadLocalwithInitial-gt;新的ConcurrentHashMap64;公共静态lt;Tgt;voidaddStringkey,TvalueCONTEXTgetputkey,value;>>TgetStringkeypublicstaticMaplt;String,Objectgt;getpublicstaticvoidremove这样,如果我们需要传递不同的参数,可以直接用一个ThreadLocal来替代多个ThreadLocal。如果我不想这样玩,我只想创建多个ThreadLocal。这是我的要求,性能一定要好。这可以实施吗?可以使用Netty的FastThreadLocal来解决这个题,但是与FastThreadLocalThread或其线程的子类配合会更加高效。更多关于它的使用,大家可以自行参考资料。
我们先看一下它的哈希函数。
//生成哈希码间隙作为这个幻数,以便生成的值或ThreadLocalID可以更均匀地分布在2的幂数组中。私有静态最终intHASH_INCRMENT=0x61c88647;/返回下一个哈希码/privatestaticintnextHashCode可以看出,它在最后构造的ThreadLocalID/threadLocalHashCode上添加了一个幻数0x61c88647。这个神奇数字的选择与斐波那契哈希有关。0x61c88647对应的十进制是1640531527,当我们用0x61c88647这个幻数进行累加,并为每个ThreadLocal分配自己的ID,即threadLocalHashCode,然后对其取2的幂取模,结果就得到了非常均匀的分布。我们还可以通过这个神奇的数字来演示
publicclassMagicHashCodeprivatestaticvoidhashCodeIntegerlengthSystemoutprintln;运行结果
不得不佩服作者使用了斐波那契哈希方法,保证了哈希表的分散性,使得结果均匀。可见“代码一定要写好,数学还是少不了的”。其他源码我就不分析了,有兴趣的可以自行查看。
线程本地内存泄漏
ThreadLocal是否会造成内存泄漏也是一个比较有争议的题。首先我们要知道什么是内存泄漏?
在Java中,内存泄漏是指某些已分配对象的存在。这些对象具有以下两个特征。首先,这些对象是可达的,即在有向图中,存在可以连接到它们的路径;其次,这些东西是没有用的。即程序以后不会使用这些对象。如果对象满足这两个条件,那么这些对象就可以被判断为Java中的内存泄漏。这些对象不会被GC回收,但它们会占用内存。
线程本地内存泄漏
ThreadLocalGCThreadLocalThreadLocalMapkeynullEntryEntry值set、getkeynullEntryEntryvalueEntryThreadLocalMapEntry[]tableEntryvalueGC
publicstaticvoidmainString[]argsthrowsInterruptedExceptionThreadsleep50000;//删除强引用threadLocal=null;系统GC;系统运行终结;系统GC;私有静态无效runThreadLocallt;Long[]gt;threadLocal捕获InterruptedExceptione启动;内置工具jconsoleexe会发现,即使执行gc,内存也不会减少,因为key仍然被线程强引用。效果图如下
ThreadLocalMap已设置、获取、删除cleanSomeSlots、expun
关于c thread传递参数和一些关于std::thread传递参数相关内容,本文都有做详细解,希望对诸位有所帮助。
发表评论:
◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。