代码之家  ›  专栏  ›  技术社区  ›  Sarmun

ThreadLocal变量的性能

  •  82
  • Sarmun  · 技术社区  · 17 年前

    读了多少 ThreadLocal

    更具体地说,创建简单对象比访问 线程本地 变量

    我假设它足够快,所以 ThreadLocal<MessageDigest> 实例比创建的实例快得多 MessageDigest

    编辑:问题是打电话时到底发生了什么 线程本地 让我们开始吧?若这只是一个领域,就像其他领域一样,那个么答案应该是“它总是最快的”,对吗?

    6 回复  |  直到 14 年前
        1
  •  57
  •   Lii bob    5 年前

    ThreadLocal 使用不同步的 HashMap 在 Thread.currentThread() 对象这使得它的速度非常快(当然,虽然没有使用常规的现场访问速度快),并且确保 线程本地 物体在爆炸时被清理干净了 Thread 死亡。在2016年更新这个答案时,似乎大多数(所有?)较新的JVM都使用 ThreadLocalMap 使用线性探测。我不确定这些功能的性能,但我无法想象它会比早期的实现更差。

    当然 new Object()

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

        2
  •  41
  •   Tom Hawtin - tackline    17 年前

    运行未发布的基准, ThreadLocal.get 在我的机器上,每次迭代大约需要35个周期。不是很多。在Sun的实现中,在 Thread 地图 ThreadLocal 它与价值观有关。因为只有一个线程可以访问它,所以它可以非常快。

    建筑 MessageDigest 可能会相对昂贵。它有相当数量的国家和建设通过 Provider SPI机制。例如,您可以通过克隆或提供 供应商 .

    只是因为它可能会更快地缓存在 线程本地 而不是创建并不一定意味着系统性能会提高。您将有额外的与GC相关的开销,这会减慢一切。

    消息摘要 您可能需要考虑使用常规线程安全高速缓存来代替。

        3
  •  35
  •   axel22    14 年前

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

    这些数字如下:

    i7

    规格:英特尔i7 2x四核@2.67 GHz 测试:scala.threads.ParallelTests

    测试名称:循环\u堆\u读取

    线程数:1 测试总数:200

    运行时间:(显示最后5个) 9.0069.0036 9.0017 9.0084 9.0074(平均值=9.1034最小值=8.9986最大值=21.0306)

    线程数:2

    运行时间:(显示最后5个)

    线程数:4 测试总数:200

    运行时间:(显示最后5个)

    线程数:8 测试总数:200

    运行时间:(显示最后5个) 2.4479 2.4362 2.4323 2.4472 2.4383(平均值=2.5562最小值=2.4166最大值=10.3726)

    线程数:1 测试总数:200

    运行时间:(显示最后5个)

    线程数:2 测试总数:200

    运行时间:(显示最后5个)

    线程数:4 测试总数:200

    运行时间:(显示最后5个) 22.8118 22.8135 59.1753 22.8229 22.8172(平均值=23.9752最小值=22.7951最大值=59.1753)

    线程数:8 测试总数:200

    22.2965 22.2415 22.3438 22.3109 22.4460(平均值=23.2676最小值=22.2346最大值=50.3583)

    AMD

    规格:AMD 8220 4x双核@2.8 GHz 测试:scala.threads.ParallelTests

    测试名称:循环\u堆\u读取

    线程数:1 测试总数:200

    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

    5.389 5.795 10.818 3.823 3.824(平均值=5.5810最小值=2.405最大值=19.755)

    测试名称:threadlocal

    线程数:1

    线程数:2 测试总数:200

    运行时间:(显示最后5个)

    线程数:4 测试总数:200

    运行时间:(显示最后5个) 62.101 67.629 62.087 52.021 55.766(平均值=65.6361最小值=50.282最大值=167.433)

    线程数:8

    运行时间:(显示最后5个) 40.672 74.301 34.434 41.549 28.119(平均值=54.7701最小值=28.119最大值=94.424)

    本地线程大约是堆读取的10-20倍。它似乎还可以很好地扩展这个JVM实现和这些具有处理器数量的架构。

        4
  •  3
  •   Gareth Davis    17 年前

    @在你优化之前,皮特是正确的。

    未使用ThreadLocal可能是泄漏和悬空引用的来源,这些引用没有明确的生命周期,通常我不会在没有明确计划何时删除特定资源的情况下使用ThreadLocal。

        5
  •  3
  •   jpereira    9 年前

    这是另一个测试。结果表明,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端螺纹局部样品:6015

    1-运行现场样品

    单端现场样品:5095

    1运行线程本地示例

    2-运行现场样品

    双端现场样本:4842

    2运行线程本地示例

    双端螺纹局部样品:5835

    三端字段示例:4674

    4-运行现场样品

    4端现场样本:4849

    四端螺纹局部样品:5309

    5端现场样品:4781

    5端螺纹局部样品:5330

    6-运行现场样品

    6运行线程本地示例

    六端螺纹局部样品:5511

    7-运行现场样品

    7运行线程本地示例

    7端螺纹局部样本:5793

    8-运行现场样品

    8端字段样本:4977

    8运行线程本地示例

    9-运行现场样品

    9端现场样本:4841

    9运行线程本地示例

    ThreadLocal平均值:5664

    环境:

    openjdk版本“1.8.0131”

    Ubuntu 16.04 LTS

        6
  •  0
  •   Pete Kirkham    17 年前

    建造它并测量它。

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