ThreadLocalRandom与共享静态随机实例性能比较测试

时间:2013-11-28 11:37:44

标签: java multithreading performance random thread-local

在我们针对一项任务的项目中,我们使用静态随机实例进行随机数生成目标。在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实例至少提高速度,但看不出任何差别。

有人可以解释为什么它会这样运作,也许我不理解某些东西。提前谢谢。

3 个答案:

答案 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的真正候选者。