为什么ThreadLocalRandom实现如此奇怪?

时间:2016-11-15 21:31:22

标签: java random implementation

此问题涉及OpenJDK 1.8.0版本中ThreadLocalRandom的实现。

ThreadLocalRandom提供了一个每线程随机数生成器,没有Random强加的同步开销。最明显的实现(IMO)将是这样的,它似乎保持向后兼容性而没有太多复杂性:

public class ThreadLocalRandom extends Random {
    private static final ThreadLocal<ThreadLocalRandom> tl =
        ThreadLocal.withInitial(ThreadLocalRandom::new);
    public static ThreadLocalRandom current() {
        return tl.get();
    }
    // Random methods moved here without synchronization
    // stream methods here
}

public class Random {
    private ThreadLocalRandom delegate = new ThreadLocalRandom();
    // methods synchronize and delegate for backward compatibility
}

然而,实际的实施是完全不同的,非常奇怪:

  • ThreadLocalRandom重复Random逐字的部分方法和其他略有修改的方法;当然,很多代码都可以重复使用。
  • Thread存储种子和探测变量,用于初始化`ThreadLocalRandom,违反封装;
  • ThreadLocalRandom使用Unsafe来访问Thread中的变量,我想这是因为这两个类在不同的包中,但状态变量必须在Thread中是私有的 - 只有封装违规才需要Unsafe;
  • ThreadLocalRandom将其下一个nextGaussian存储在静态ThreadLocal中,而不是Random中的实例变量中。

总的来说,我粗略的检查似乎揭示了Random的丑陋副本,与上面的简单实现没有任何优势。但是标准库的作者是聪明的,所以必须有一些这种奇怪方法的原因。有没有人知道为什么ThreadLocalRandom以这种方式实施?

1 个答案:

答案 0 :(得分:9)

关键问题是许多代码都是遗留的,无法(轻松)更改 - Random被设计为&#34;线程安全&#34;通过同步其所有方法。这是有效的,因为Random的实例可以跨多个线程使用,但它是一个严重的瓶颈,因为没有两个线程可以同时检索随机数据。一个简单的解决方案是构造一个ThreadLocal<Random>对象,从而避免锁争用,但这仍然不是理想的。即使在无争议的情况下,synchronized方法仍有一些开销,构建 n Random个实例时,它们会浪费所有基本上都做同样的工作。

因此,在高级别ThreadLocalRandom作为性能优化存在,因此它的实现将是&#34;奇怪的&#34;,因为JDK开发人员已经花时间优化它。< / p>

JDK中有许多类,乍一看是丑陋的&#34;。但请记住,JDK作者正在解决与您不同的问题。他们编写的代码将以数千甚至数百万的开发人员以无数方式使用。他们必须定期权衡最佳实践以提高效率,因为他们编写的代码非常重要。

Effective Java: Item 55也解决了这个问题 - 关键是优化应该由专家作为最后的手段来完成。 JDK开发人员 那些专家。

针对您的具体问题:

  

ThreadLocalRandom重复Random逐字的部分方法和其他经过微小修改的方法;肯定会有很多代码可以重复使用。

不幸的是,因为Random上的方法是synchronized。如果调用它们ThreadLocalRandom会导致Random锁定争用问题。 TLR 需要覆盖每个方法,以便从方法中删除synchronized关键字。

  

Thread存储种子和用于初始化ThreadLocalRandom的探测变量,违反了封装;

首先,它真的没有&#34;违反封装&#34;因为该字段仍然是包私有的。它是由用户封装的,这是目标。我不会对此过于依赖,因为这里的决定是为了提高性能。有时性能是以正常的良好设计为代价的。在实践中,这违反了#34;是不可检测的。行为简单地封装在两个类中而不是单个类中。

将种子放在Thread中允许ThreadLocalRandom完全无状态(除了initialized字段,这在很大程度上是不相关的),因此只需要存在一个实例整个申请。

  

ThreadLocalRandom使用Unsafe来访问Thread中的变量,我想这是因为这两个类在不同的包中,但状态变量必须在Thread中是私有的 - 只有封装违规才需要Unsafe;

许多JDK类使用Unsafe。它是一种工具,而不是罪恶。再说一遍,我对这个事实不会感到太紧张。该类称为Unsafe,以阻止非专业开发人员滥用它。我们相信/希望JDK作者足够聪明,知道什么时候可以安全使用。

  

ThreadLocalRandom将其下一个nextGaussian存储在静态ThreadLocal中,而不是Random中的实例变量中。

由于只有ThreadLocalRandom的一个实例,因此不需要将其作为实例变量。我想你也可以说它不需要static,但那时你只是辩论风格。至少使static更明确地让班级基本上无国籍。作为文件中的mentioned,此字段不是必需的,但可确保与Random类似的行为。