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

Java ThreadLocalRandom与共享静态随机实例性能比较测试,java,multithreading,performance,random,thread-local,Java,Multithreading,Performance,Random,Thread Local,在我们的一个任务项目中,我们使用静态随机实例来实现随机数生成目标。Java7发布后,出现了新的ThreadLocalRandom类,用于生成随机数 从规格: 如果适用,在并发程序中使用ThreadLocalRandom而不是共享随机对象通常会遇到更少的开销和争用。当多个任务(例如,每个任务都是ForkJoinTask)在线程池中并行使用随机数时,使用ThreadLocalRandom尤其合适 而且: 当所有用法都是这种形式时,不可能在多个线程之间意外共享ThreadLocalRandom 所以我

在我们的一个任务项目中,我们使用静态随机实例来实现随机数生成目标。Java7发布后,出现了新的ThreadLocalRandom类,用于生成随机数

从规格:

如果适用,在并发程序中使用ThreadLocalRandom而不是共享随机对象通常会遇到更少的开销和争用。当多个任务(例如,每个任务都是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;
}
对于我来说,这有点奇怪,我希望使用ThreadLocalRandom时,与共享的随机实例相比,速度至少会有所提高,但没有看到任何区别


有人能解释为什么会这样,也许我不明白什么。提前感谢。

您的测试代码有一个缺陷。到处都是标杆的祸根

thread.start();
thread.join();
为什么不保存LOC并写入

thread.run();
结果是一样的


编辑:如果您没有意识到上面的结果,这意味着您正在运行单线程测试,没有多线程运行。

您没有并行运行任何东西,因为您正在等待每个线程在启动后立即完成。在启动线程的循环外需要一个等待循环:

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();
}
List threads=new ArrayList();
对于(int i=0;i
也许只需看看实际发生的情况就更容易了。以下是
ThreadLocal.get()
的源代码,它也被
ThreadLocalRandom.current()
调用

其中,ThreadLocalMap是一个具有优化功能的类似HashMap的专用实现

所以基本上发生的是ThreadLocal持有一个映射线程->对象-或者在本例中是线程->随机-然后被查找并返回或创建。因为这并不是什么“神奇的”,所以计时将等于HashMap查找+要返回的实际对象的初始创建开销。由于HashMap查找(在此优化情况下)是线性的,因此查找的成本为k,其中k是哈希函数的计算成本

所以你可以做一些假设:

  • ThreadLocal将比每次在每个Runnable中创建对象都要快,除非创建成本比k小得多。所以随机查找是一件好事,在里面放一个int可能不是那么聪明

  • ThreadLocal将比使用您自己的HashMap更好,因为可以假设这样的通用实现等于k或更差

  • ThreadLocal将比使用成本myRandoms[threadID]
    但是,这假设您首先知道哪些线程将处理您的工作,因此无论如何,这不是ThreadLocal的真正候选对象


让我模仿一下专家:微基准很难做对。如果你想尝试,就用它来做,首先,你的线程不是并行的,因为你在启动一个线程之后,然后再启动另一个线程。开销和争用的减少并不意味着它更快,只是意味着在某些类型和级别的压力下,可能会提高吞吐量和稳定性。如果你仍然对微基准测试结果感兴趣,本博客展示了对这些东西进行基准测试的挑战:从操作系统的角度来看:
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();
}
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();
}