有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

ThreadLocal变量的java性能

ThreadLocal变量读取的速度比从常规字段读取的慢多少

更具体地说,创建简单对象比访问ThreadLocal变量快还是慢

我假设它足够快,所以拥有ThreadLocal<MessageDigest>实例比每次创建MessageDigest实例要快得多。但这是否也适用于字节[10]或字节[1000]等

编辑:问题是调用ThreadLocal的get时到底发生了什么?如果这只是一个领域,就像其他领域一样,那么答案应该是“它总是最快的”,对吗


共 (6) 个答案

  1. # 1 楼答案

    这是另一个测试。结果表明,ThreadLocal比常规字段稍微慢一点,但顺序相同。大约慢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 Map<String,String> map = new HashMap<String, String>();
    
        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<Map<String,String>> myThreadLocal = new ThreadLocal<Map<String,String>>() {
            @Override protected Map<String, String> initialValue() {
                return new HashMap<String, String>();
            }
        };
    
        public void run(int z) {
            System.out.println(z+"-Running thread local sample");
            long start = System.currentTimeMillis();
            for (int i = 0; i < N; i++){
                String s = Integer.toString(i);
                myThreadLocal.get().put(s, "a");
                myThreadLocal.get().remove(s);
            }
            long end = System.currentTimeMillis();
            long t = (end - start);
            threadLocalExecTime += t;
            System.out.println(z+"-End thread local sample:"+t);
        }
    }
    }'
    

    输出:

    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. # 2 楼答案

    2009年,一些JVM在Thread.currentThread()对象中使用一个不同步的HashMap实现了ThreadLocal。这使得它的速度非常快(当然,虽然没有使用常规字段访问的速度快),并且确保ThreadLocal对象在Thread死亡时得到整理。2016年更新了这个答案,似乎是大多数(全部?)较新的JVM使用带有线性探测的ThreadLocalMap。我不确定这些系统的性能——但我无法想象它会比早期的实现严重得多

    当然,new Object()现在也非常快,垃圾收集器也非常擅长回收短期对象

    除非您确定创建对象的成本会很高,或者需要逐个线程地保持某种状态,否则最好使用更简单的allocate when needed解决方案,只有当探查器告诉您需要时才切换到ThreadLocal实现

  3. # 3 楼答案

    建造并测量它

    此外,如果将消息消化行为封装到对象中,则只需要一个threadlocal。如果出于某种目的需要一个本地MessageDigest和一个本地字节[1000],请创建一个带有MessageDigest和字节[]字段的对象,并将该对象放入ThreadLocal中,而不是单独放入两者

  4. # 4 楼答案

    好问题,我最近一直在问自己。为了给出明确的数字,下面的基准测试(在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)
    }
    

    可用的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. # 5 楼答案

    在我的机器上运行未发布的基准测试,每次迭代大约需要35个周期。不是很多。在Sun的实现中,Thread中的自定义线性探测哈希映射将ThreadLocal映射为值。因为只有一个线程可以访问它,所以它可以非常快

    小对象的分配需要相似的周期数,尽管由于缓存耗尽,在一个紧循环中,您可能会得到更低的数字

    构建MessageDigest可能相对昂贵。它有相当多的状态,并且通过ProviderSPI机制进行构建。例如,您可以通过克隆或提供Provider来优化

    仅仅因为缓存在ThreadLocal中比创建缓存更快,并不一定意味着系统性能会提高。你会有与GC相关的额外开销,这会减慢一切

    除非您的应用程序大量使用^ {CD4>},否则您可能需要考虑使用常规的线程安全高速缓存。p>

  6. # 6 楼答案

    @Pete是正确的,在你优化之前先测试一下

    如果与实际使用MessageDigest相比,构建MessageDigest有任何严重的开销,我会非常惊讶

    错过使用ThreadLocal可能会导致泄漏和悬而未决的引用,这些引用没有明确的生命周期,通常我不会在没有明确计划的情况下使用ThreadLocal,以确定何时删除特定资源