在我们针对一项任务的项目中,我们使用静态随机实例进行随机数生成目标。在Java 7发布之后,出现了新的ThreadLocalRandom类用于生成随机数。
来自规范:
如果适用,在并发程序中使用ThreadLocalRandom而不是共享Random对象通常会遇到更少的开销和争用。当多个任务(例如,每个ForkJoinTask)在线程池中并行使用随机数时,使用ThreadLocalRandom是特别合适的。
还有:
当所有用法都是这种形式时,永远不可能在多个线程中意外地共享ThreadLocalRandom。
所以我做了一点测试:
public class ThreadLocalRandomTest {
private static final int THREAD_COUNT = 100;
private static final int GENERATED_NUMBER_COUNT = 1000;
private static final int INT_RIGHT_BORDER = 5000;
private static final int EXPERIMENTS_COUNT = 5000;
public static void main(String[] args) throws InterruptedException {
System.out.println("Number of threads: " + THREAD_COUNT);
System.out.println("Length of generated numbers chain for each thread: " + GENERATED_NUMBER_COUNT);
System.out.println("Right border integer: " + INT_RIGHT_BORDER);
System.out.println("Count of experiments: " + EXPERIMENTS_COUNT);
int repeats = 0;
int workingTime = 0;
long startTime = 0;
long endTime = 0;
for (int i = 0; i < EXPERIMENTS_COUNT; i++) {
startTime = System.currentTimeMillis();
repeats += calculateRepeatsForSharedRandom();
endTime = System.currentTimeMillis();
workingTime += endTime - startTime;
}
System.out.println("Average repeats for shared Random instance: " + repeats / EXPERIMENTS_COUNT
+ ". Average working time: " + workingTime / EXPERIMENTS_COUNT + " ms.");
repeats = 0;
workingTime = 0;
for (int i = 0; i < EXPERIMENTS_COUNT; i++) {
startTime = System.currentTimeMillis();
repeats += calculateRepeatsForTheadLocalRandom();
endTime = System.currentTimeMillis();
workingTime += endTime - startTime;
}
System.out.println("Average repeats for ThreadLocalRandom: " + repeats / EXPERIMENTS_COUNT
+ ". Average working time: " + workingTime / EXPERIMENTS_COUNT + " ms.");
}
private static int calculateRepeatsForSharedRandom() throws InterruptedException {
final Random rand = new Random();
final Map<Integer, Integer> counts = new HashMap<>();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread() {
@Override
public void run() {
for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) {
int random = rand.nextInt(INT_RIGHT_BORDER);
if (!counts.containsKey(random)) {
counts.put(random, 0);
}
counts.put(random, counts.get(random) + 1);
}
}
};
thread.start();
thread.join();
}
int repeats = 0;
for (Integer value : counts.values()) {
if (value > 1) {
repeats += value;
}
}
return repeats;
}
private static int calculateRepeatsForTheadLocalRandom() throws InterruptedException {
final Map<Integer, Integer> counts = new HashMap<>();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread() {
@Override
public void run() {
for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) {
int random = ThreadLocalRandom.current().nextInt(INT_RIGHT_BORDER);
if (!counts.containsKey(random)) {
counts.put(random, 0);
}
counts.put(random, counts.get(random) + 1);
}
}
};
thread.start();
thread.join();
}
int repeats = 0;
for (Integer value : counts.values()) {
if (value > 1) {
repeats += value;
}
}
return repeats;
}
}
我还添加了非共享随机测试,并得到了下一个结果:
Number of threads: 100
Length of generated numbers chain for each thread: 100
Right border integer: 5000
Count of experiments: 10000
Average repeats for non-shared Random instance: 8646. Average working time: 13 ms.
Average repeats for shared Random instance: 8646. Average working time: 13 ms.
Average repeats for ThreadLocalRandom: 8646. Average working time: 13 ms.
至于我,这有点奇怪,我预计使用ThreadLocalRandom比共享的Random实例至少提高速度,但看不出任何差别。
有人可以解释为什么它会这样运作,也许我不理解某些东西。提前谢谢。
答案 0 :(得分:3)
您的测试代码存在缺陷。到处都是基准测试员的祸根。
thread.start();
thread.join();
为什么不保存LOC并写
thread.run();
结果是一样的。
编辑:如果你没有意识到上述结果,那就意味着你正在运行单线程测试,没有多线程正在进行。
答案 1 :(得分:3)
你没有并行运行任何东西,因为你正在等待每个线程在启动后立即完成。您需要在循环外部启动线程的等待循环:
List<Thread> threads = new ArrayList<Thread>();
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread() {
@Override
public void run() {
for (int j = 0; j < GENERATED_NUMBER_COUNT; j++) {
int random = rand.nextInt(INT_RIGHT_BORDER);
if (!counts.containsKey(random)) {
counts.put(random, 0);
}
counts.put(random, counts.get(random) + 1);
}
}
};
threads.add(thread);
thread.start();
}
for (Thread thread: threads) {
thread.join();
}
答案 2 :(得分:1)
也许看看实际发生的事情会更容易。以下是ThreadLocal.get()
的来源,也是ThreadLocalRandom.current()
。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap是一个具有优化功能的专用HashMap实现。
所以基本上发生的是ThreadLocal持有一个地图Thread-&gt; Object - 或者在这种情况下是Thread-&gt; Random-然后查找并返回或创建。因为这不是什么“神奇”,时间将等于HashMap-lookup +要返回的实际Object的初始创建开销。由于HashMap查找(在此优化情况下)是线性的,因此查找的成本是 k ,其中 k 是哈希函数的计算成本。
所以你可以做一些假设:
ThreadLocal将比每次Runnable中创建对象更快,除非创建成本远小于 k 。因此查找Random是一件好事,将int放入内部可能不那么聪明。
ThreadLocal将比使用您自己的HashMap更好,因为可以假设这样的通用实现等于 k 或更糟。
ThreadLocal将比使用任何成本&lt;的查找慢。 ķ。示例:首先将所有内容存储在数组中,然后执行myRandoms[threadID]
但是这假设你知道哪些线程将首先处理你的工作,所以这不是ThreadLocal的真正候选者。