java.util.Random和concurrent.ThreadLocalRandom使用对比

 更新时间:2024年07月04日 10:33:14   作者:xindoo  
这篇文章主要介绍了java.util.Random和concurrent.ThreadLocalRandom使用对比,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

java.util.Random和concurrent.ThreadLocalRandom对比

最近工作中遇到了一个需求

需要以一定的概率过滤掉一部分的流量,想想只能用Random了,因为是在多线程环境下,我还特意确认了下Random在多线程是否能正常运行,Random的实现也比较简单,初始化的时候用当前的事件来初始化一个随机数种子,然后每次取值的时候用这个种子与有些MagicNumber运算,并更新种子。

最核心的就是这个next的函数,不管你是调用了nextDouble还是nextInt还是nextBoolean,Random底层都是调这个next(int bits)。

    protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

为了保证多线程下每次生成随机数都是用的不同

next()得保证seed的更新是原子操作,所以用了AtomicLong的compareAndSet(),该方法底层调用了sum.misc.Unsafe的compareAndSwapLong(),也就是大家常听到的CAS, 这是一个native方法,它能保证原子更新一个数。  

既然Random满足我的需求,又能在多线程下正常运行,所以我直接用了random,后来在codeReview中,同事提出用concurrent.ThreadLocalRandom来替代Random。

我脑子里立马冒出一个问题,既然random是线程安全的,为什么concurrent包里还要实现一个random。

在oracle的jdk文档里发现这样一句话:

use of ThreadLocalRandom rather than shared Random objects in concurrent programs will typically encounter much less overhead and contention. Use of ThreadLocalRandom is particularly appropriate when multiple tasks (for example, each a ForkJoinTask) use random numbers in parallel in thread pools.

大意就是用ThreadLocalRandom更适合用在多线程下,能大幅减少多线程并行下的性能开销和资源争抢。  

既然文档里说的牛,到底能有多少的性能提升?

我做了一个简单的测试。

测试环境:

24核 CPU, jdk8,每个随机生成100000个double数,分别测试不同线程数下rando和ThreadLocalRandom的运行时间,数据如下:

这里写图片描述

ThreadNum,Random,ThreadLocalRandom 
50,1192,575
100,4031,162
150,6068,223
200,8093,287
250,10049,248
300,12346,200
350,14429,212
400,16491,62
450,18475,96
500,11311,97
550,12421,90
600,13577,102
650,14718,111
700,15896,127
750,17101,129
800,17907,203
850,19261,226
900,21576,151
950,22206,147
1000,23418,174

ThreadLocalRandom虽然也有波动,但基本上是平的,而random随着线程数的增加一直在增加,在1000个线程时两者居然有百倍的性能差距。

不过这里有个让人百思不得其解的现象,为什么random的耗时在500个线程的时候又掉下来,测试多次都是这个情况,可见并不是偶发现象。

我也在本人的笔记本上测了下,我笔记本双核i7,ThreadLocalRandom和Random性能差距最高也有100倍,我发现我笔记本比公司服务器跑的快(数据如下)。。。。

我也在一台1核的阿里云ECS上测试了,按道理1核心的技术上,即便是多线程起始也是串行执行的,但ThreadLocalRandom和Random在1000个线程的情况下也有6倍的性能差距。

  这里写图片描述

既然ThreadLocalRandom在多线程下表现这么牛,它究竟是如何做到的?

我们来看下源码,它的核心代码是这个:

    final long nextSeed() {
        Thread t; long r; // read and update per-thread seed
        UNSAFE.putLong(t = Thread.currentThread(), SEED,
                       r = UNSAFE.getLong(t, SEED) + GAMMA);
        return r;
    }

起始ThreadLocalRandom是对每个线程都设置了单独的随机数种子,这样就不会发生多线程同时更新一个数时产生的资源争抢了,用空间换时间。  

附上Random和ThreadLocalRandom的性能测试代码

import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class RandomTest {
    private static Random random = new Random();

    private static final int N = 100000;
//    Random from java.util.concurrent.
    private static class TLRandom implements Runnable {
        @Override
        public void run() {
            double x = 0;
            for (int i = 0; i < N; i++) {
                x += ThreadLocalRandom.current().nextDouble();
            }
        }
    }

//    Random from java.util
    private static class URandom implements Runnable {
        @Override
        public void run() {
            double x = 0;
            for (int i = 0; i < N; i++) {
                x += random.nextDouble();
            }
        }
    }

    public static void main(String[] args) {
        System.out.println("threadNum,Random,ThreadLocalRandom");
        for (int threadNum = 50; threadNum <= 2000; threadNum += 50) {
            ExecutorService poolR = Executors.newFixedThreadPool(threadNum);
            long RStartTime = System.currentTimeMillis();
            for (int i = 0; i < threadNum; i++) {
                poolR.execute(new URandom());
            }
            try {
                poolR.shutdown();
                poolR.awaitTermination(100, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            String str = "" + threadNum +"," + (System.currentTimeMillis() - RStartTime)+",";

            ExecutorService poolTLR = Executors.newFixedThreadPool(threadNum);
            long TLRStartTime = System.currentTimeMillis();
            for (int i = 0; i < threadNum; i++) {
                poolTLR.execute(new TLRandom());
            }
            try {
                poolTLR.shutdown();
                poolTLR.awaitTermination(100, TimeUnit.SECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(str + (System.currentTimeMillis() - TLRStartTime));
        }
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 使用Springboot+Vue实现文件上传和下载功能

    使用Springboot+Vue实现文件上传和下载功能

    本文介绍了如何使用Springboot结合Vue进行图书信息管理系统开发,包括数据库表的创建,实体类、Dao层、Service层和Controller层的编写,重点讲解了文件上传和下载功能的实现,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • C# 中Excel导入时判断是否被占用三种方法

    C# 中Excel导入时判断是否被占用三种方法

    这篇文章主要介绍了C# 中Excel导入时 判断是否被占用三种方法的相关资料,需要的朋友可以参考下
    2017-04-04
  • 详解Java的Hibernate框架中的注解与缓存

    详解Java的Hibernate框架中的注解与缓存

    这篇文章主要介绍了详解Java的Hibernate框架中的注解与缓存,Hibernate是Java的SSH三大web开发框架之一,需要的朋友可以参考下
    2015-12-12
  • java日期处理工具类

    java日期处理工具类

    这篇文章主要为大家详细介绍了java日期处理工具类,其次还介绍了日期处理的基础知识,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-05-05
  • SpringBoot+MyBatis实现数据库字段级加密

    SpringBoot+MyBatis实现数据库字段级加密

    在数据安全越来越受重视的今天,如何保护用户的敏感信息成为每个开发者都要面对的问题,本文将分享一个基于注解的自动加解密方案,感兴趣的小伙伴可以了解下
    2025-11-11
  • Java中的Schema校验技术与实践示例详解

    Java中的Schema校验技术与实践示例详解

    本主题详细介绍了在Java环境下进行XML Schema和JSON Schema校验的方法,包括使用JAXP、JAXB以及专门的JSON校验库等技术,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧
    2025-09-09
  • 深入了解Java核心类库--Arrays类

    深入了解Java核心类库--Arrays类

    这篇文章主要为大家详细介绍了java Arrays类定义与使用的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能给你带来帮助
    2021-07-07
  • Java中操作Word修订功能的示例详解

    Java中操作Word修订功能的示例详解

    Word的修订功能是一种在文档中进行编辑和审阅的功能,它允许多个用户对同一文档进行修改并跟踪这些修改,以便进行审查和接受或拒绝修改,下面我们就来学习一下Java中操作Word修订功能的方法,需要的可以参考下
    2023-12-12
  • Spring Boot 钩子全集实战EnvironmentPostProcessor全解

    Spring Boot 钩子全集实战EnvironmentPostProcessor全解

    文章详细介绍了SpringBoot中的EnvironmentPostProcessor扩展点,该点在配置加载阶段提供强大的定制化能力,适用于配置中心化、加密解密、动态覆盖和校验等场景,感兴趣的朋友跟随小编一起看看吧
    2026-01-01
  • spring-mybatis获取mapper的四种方式汇总

    spring-mybatis获取mapper的四种方式汇总

    这篇文章主要介绍了spring-mybatis获取mapper的四种方式汇总,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-03-03

最新评论