从ThreadLocal
变量读取多少比常规字段慢?
更具体地说,简单的对象创建比访问ThreadLocal
变量更快还是更慢?
我认为它足够快,因此ThreadLocal
实例比MessageDigest
每次创建实例要快得多.但这也适用于字节[10]或字节[1000]吗?
编辑:问题是在调用ThreadLocal
get 时真正发生的事情?如果那只是一个领域,就像任何其他领域一样,那么答案就是"它总是最快",对吧?
在2009年,一些JVM使用Thread.currentThread()对象中的非同步HashMap实现了ThreadLocal.这使得它非常快(当然,并不像使用常规字段访问那么快),以及确保在线程死亡时ThreadLocal对象得到了整理.在2016年更新这个答案,似乎大多数(所有?)较新的JVM使用线性探测的ThreadLocalMap.我不确定那些的表现 - 但我无法想象它比早期的实施更糟糕.
当然,新的Object()现在也非常快,垃圾收集器也非常擅长回收短期对象.
除非您确定对象创建会很昂贵,或者您需要逐个线程地保持某个状态,所以最好在需要的解决方案时进行更简单的分配,并且只在切换到ThreadLocal实现时探查器告诉你,你需要.
运行未发布的基准测试,ThreadLocal.get
在我的机器上每次迭代需要大约35个周期.没什么大不了的.在Sun的实现自定义的线性探测哈希表的Thread
映射ThreadLocal
s到值.因为它只能由单个线程访问,所以它可以非常快.
小对象的分配需要相似数量的周期,尽管由于高速缓存耗尽,您可能会在紧密循环中获得稍低的数字.
建设MessageDigest
可能相对昂贵.它有相当数量的州和建设通过Provider
SPI机制.您可以通过克隆或提供来优化Provider
.
仅仅因为在一个ThreadLocal
而不是创建中缓存可能更快并不一定意味着系统性能会提高.您将获得与GC相关的额外开销,这会减慢所有速度.
除非您的应用程序使用频繁,否则您MessageDigest
可能需要考虑使用传统的线程安全缓存.
好问题,我最近一直在问自己.为了给出明确的数字,下面的基准测试(在Scala中,编译成与等效Java代码几乎相同的字节码):
var cnt: String = "" val tlocal = new java.lang.ThreadLocal[String] { override def initialValue = "" } def loop_heap_write = { var i = 0 val until = totalwork / threadnum while (i < until) { if (cnt ne "") cnt = "!" i += 1 } cnt } def threadlocal = { var i = 0 val until = totalwork / threadnum while (i < until) { if (tlocal.get eq null) i = until + i + 1 i += 1 } if (i > until) println("thread local value was null " + i) }
这里提供的是AMD 4x 2.8 GHz双核和具有超线程(2.67 GHz)的四核i7.
这些是数字:
规格:Intel i7 2x四核@ 2.67 GHz测试:scala.threads.ParallelTests
测试名称:loop_heap_read
螺纹数:1总测试:200
运行时间:(显示最后5个)9.0069 9.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(avg = 4.6337 min = 4.5509 max = 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(avg = 23.2676 min = 22.2346 max = 50.3583)
规格:AMD 8220 4x双核@ 2.8 GHz测试:scala.threads.ParallelTests
测试名称:loop_heap_read
总工作量:20000000螺纹数:1总测试:200
运行时间:(显示最后5)12.625 12.631 12.634 12.632 12.628(平均值= 12.7333最小值= 12.619最大值= 26.698)
测试名称:loop_heap_read总工作量:20000000
运行时间:(显示最后5)6.412 6.424 6.408 6.397 6.43(平均值= 6.5367 min = 6.393 max = 19.716)
螺纹数:4总测试:200
运行时间:(显示最后5个)3.385 4.298 9.7 6.535 3.385(avg = 5.6079 min = 3.354 max = 21.603)
线程数:8总测试:200
运行时间:(显示最后5)5.389 5.795 10.818 3.823 3.824(平均值= 5.5810 min = 2.405 max = 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实现和这些具有处理器数量的架构上也能很好地扩展.
这是另一个测试.结果显示ThreadLocal比常规字段慢一点,但顺序相同.Aprox慢了12%
public class Test { private static final int N = 100000000; private static int fieldExecTime = 0; private static int threadLocalExecTime = 0; public static void main(String[] args) throws InterruptedException { int execs = 10; for (int i = 0; i < execs; i++) { new FieldExample().run(i); new ThreadLocaldExample().run(i); } System.out.println("Field avg:"+(fieldExecTime / execs)); System.out.println("ThreadLocal avg:"+(threadLocalExecTime / execs)); } private static class FieldExample { private Mapmap = new HashMap (); public void run(int z) { System.out.println(z+"-Running field sample"); long start = System.currentTimeMillis(); for (int i = 0; i < N; i++){ String s = Integer.toString(i); map.put(s,"a"); map.remove(s); } long end = System.currentTimeMillis(); long t = (end - start); fieldExecTime += t; System.out.println(z+"-End field sample:"+t); } } private static class ThreadLocaldExample{ private ThreadLocal
输出:
0-运行场样本
0-结束场样本:6044
0-运行线程本地样本
0-结束线程本地样本:6015
1运行场样本
1-End野外样本:5095
1 - 运行线程本地样本
1端螺纹局部样品:5720
2运行场样本
2-End野外样本:4842
2-运行线程本地样本
2端线程本地样本:5835
3运行场样本
3-End野外样本:4674
3-运行线程本地样本
3端线程本地样本:5287
4运行场样本
4-End野外样本:4849
4-运行线程本地样本
4端线程本地样本:5309
5运行场样本
5-End野外样本:4781
5-运行线程本地样本
5端线程本地样本:5330
6运行场样本
6-End野外样本:5294
6-运行线程本地样本
6端线程本地样本:5511
7运行场样本
7-End野外样本:5119
7-运行线程本地样本
7-End thread本地样本:5793
8-运行场样本
8-End野外样本:4977
8-运行线程本地样本
8端线程本地样本:6374
9-运行场样本
9-End野外样本:4841
9-运行线程本地样本
9端线程本地样本:5471
Field avg:5051
ThreadLocal平均值:5664
ENV:
openjdk版本"1.8.0_131"
英特尔®酷睿™i7-7500U CPU @ 2.70GHz×4
Ubuntu 16.04 LTS