此问题涉及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
以这种方式实施?
答案 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
类似的行为。