ThreadLocal变量的java性能
从ThreadLocal
变量读取的速度比从常规字段读取的慢多少
更具体地说,创建简单对象比访问ThreadLocal
变量快还是慢
我假设它足够快,所以拥有ThreadLocal<MessageDigest>
实例比每次创建MessageDigest
实例要快得多。但这是否也适用于字节[10]或字节[1000]等
编辑:问题是调用ThreadLocal
的get时到底发生了什么?如果这只是一个领域,就像其他领域一样,那么答案应该是“它总是最快的”,对吗
# 1 楼答案
这是另一个测试。结果表明,ThreadLocal比常规字段稍微慢一点,但顺序相同。大约慢12%
输出:
0-运行现场样本
0端字段样本:6044
0运行线程本地示例
0端螺纹局部样本:6015
1-运行现场样品
单端现场样本:5095
1运行线程本地示例
单端螺纹局部样本:5720
2-运行现场样品
双端现场样本:4842
2运行线程本地示例
双端螺纹局部样本:5835
3-运行现场样品
三端现场样本:4674
3运行线程本地示例
三端螺纹局部样本:5287
4-运行现场样品
4端现场样本:4849
4运行线程本地示例
4端螺纹局部样本:5309
5-运行现场样品
5端现场样本:4781
5运行线程本地示例
5端螺纹局部样本:5330
6-运行现场样品
6端现场样本:5294
6运行线程本地示例
6端螺纹局部样本:5511
7-运行现场样品
7端现场样本:5119
7运行线程本地示例
7端螺纹局部样本:5793
8-运行现场样品
8端现场样本:4977
8运行线程本地示例
8端螺纹局部样本:6374
9-运行现场样品
9端现场样本:4841
9运行线程本地示例
9端螺纹局部样本:5471
现场平均值:5051
ThreadLocal平均值:5664
环境:
openjdk版本“1.8.0131”
英特尔®核心™ i7-7500U CPU@2.70GHz×4
Ubuntu 16.04 LTS
# 2 楼答案
2009年,一些JVM在
Thread.currentThread()
对象中使用一个不同步的HashMap
实现了ThreadLocal
。这使得它的速度非常快(当然,虽然没有使用常规字段访问的速度快),并且确保ThreadLocal
对象在Thread
死亡时得到整理。2016年更新了这个答案,似乎是大多数(全部?)较新的JVM使用带有线性探测的ThreadLocalMap
。我不确定这些系统的性能——但我无法想象它会比早期的实现严重得多当然,
new Object()
现在也非常快,垃圾收集器也非常擅长回收短期对象除非您确定创建对象的成本会很高,或者需要逐个线程地保持某种状态,否则最好使用更简单的allocate when needed解决方案,只有当探查器告诉您需要时才切换到
ThreadLocal
实现# 3 楼答案
建造并测量它
此外,如果将消息消化行为封装到对象中,则只需要一个threadlocal。如果出于某种目的需要一个本地MessageDigest和一个本地字节[1000],请创建一个带有MessageDigest和字节[]字段的对象,并将该对象放入ThreadLocal中,而不是单独放入两者
# 4 楼答案
好问题,我最近一直在问自己。为了给出明确的数字,下面的基准测试(在Scala中,编译成与等效Java代码几乎相同的字节码):
可用的here是在AMD 4x 2.8 GHz双核和四核i7(2.67 GHz)上进行的
以下是数字:
i7
规格:英特尔i7 2x四核@2.67 GHz 测试:scala。线程。平行测试
测试名称:循环_堆_读取
线程数:1 测试总数:200
运行时间:(显示最后5个) 9.0069.0036 9.0017 9.0084 9.0074(平均值=9.1034最小值=8.9986最大值=21.0306)
线程数:2 测试总数:200
运行时间:(显示最后5个) 4.5563 4.7128 4.5663 4.5617 4.5724(平均值=4.6337最小值=4.5509最大值=13.9476)
线程数:4 测试总数:200
运行时间:(显示最后5个) 2.3946 2.3979 2.3934 2.3937 2.3964(平均值=2.5113最小值=2.3884最大值=13.5496)
线程数:8 测试总数:200
运行时间:(显示最后5个) 2.4479 2.4362 2.4323 2.4472 2.4383(平均值=2.5562最小值=2.4166最大值=10.3726)
测试名称:threadlocal
线程数:1 测试总数:200
运行时间:(显示最后5个) 91.1741 90.8978 90.6181 90.6200 90.6113(平均值=91.0291最小值=90.6000最大值=129.7501)
线程数:2 测试总数:200
运行时间:(显示最后5个) 45.3838 45.3858 45.6676 45.3772 45.3839(平均值=46.0555最小值=45.3726最大值=90.7108)
线程数:4 测试总数:200
运行时间:(显示最后5个) 22.8118 22.8135 59.1753 22.8229 22.8172(平均值=23.9752最小值=22.7951最大值=59.1753)
线程数:8 测试总数:200
运行时间:(显示最后5个) 22.2965 22.2415 22.3438 22.3109 22.4460(平均值=23.2676最小值=22.2346最大值=50.3583)
AMD
规格:AMD 8220 4x双核@2.8 GHz 测试:scala。线程。平行测试
测试名称:循环_堆_读取
总工作量:2000万 线程数:1 测试总数:200
运行时间:(显示最后5个) 12.625 12.631 12.634 12.632 12.628(平均值=12.7333最小值=12.619最大值=26.698)
测试名称:循环\u堆\u读取 总工作量:2000万
运行时间:(显示最后5个) 6.4126.4246.408 6.397 6.43(平均值=6.5367最小值=6.393最大值=19.716)
线程数:4 测试总数:200
运行时间:(显示最后5个) 3.385 4.298 9.7 6.535 3.385(平均值=5.6079最小值=3.354最大值=21.603)
线程数:8 测试总数:200
运行时间:(显示最后5个) 5.389 5.795 10.818 3.823 3.824(平均值=5.5810最小值=2.405最大值=19.755)
测试名称:threadlocal
线程数:1 测试总数:200
运行时间:(显示最后5个) 200.217 207.335 200.241 207.342 200.23(平均值=202.2424最小值=200.184最大值=245.369)
线程数:2 测试总数:200
运行时间:(显示最后5个) 100.208 100.199 100.211 103.781 100.215(平均值=102.2238最小值=100.192最大值=129.505)
线程数:4 测试总数:200
运行时间:(显示最后5个) 62.101 67.629 62.087 52.021 55.766(平均值=65.6361最小值=50.282最大值=167.433)
线程数:8 测试总数:200
运行时间:(显示最后5个) 40.672 74.301 34.434 41.549 28.119(平均值=54.7701最小值=28.119最大值=94.424)
摘要
本地线程大约是堆读取的10-20倍。它似乎在这个JVM实现和这些具有处理器数量的架构上也能很好地扩展
# 5 楼答案
在我的机器上运行未发布的基准测试,每次迭代大约需要35个周期。不是很多。在Sun的实现中,
Thread
中的自定义线性探测哈希映射将ThreadLocal
映射为值。因为只有一个线程可以访问它,所以它可以非常快小对象的分配需要相似的周期数,尽管由于缓存耗尽,在一个紧循环中,您可能会得到更低的数字
构建
MessageDigest
可能相对昂贵。它有相当多的状态,并且通过Provider
SPI机制进行构建。例如,您可以通过克隆或提供Provider
来优化仅仅因为缓存在
ThreadLocal
中比创建缓存更快,并不一定意味着系统性能会提高。你会有与GC相关的额外开销,这会减慢一切除非您的应用程序大量使用^ {CD4>},否则您可能需要考虑使用常规的线程安全高速缓存。p>
# 6 楼答案
@Pete是正确的,在你优化之前先测试一下
如果与实际使用MessageDigest相比,构建MessageDigest有任何严重的开销,我会非常惊讶
错过使用ThreadLocal可能会导致泄漏和悬而未决的引用,这些引用没有明确的生命周期,通常我不会在没有明确计划的情况下使用ThreadLocal,以确定何时删除特定资源