`
fly_ever
  • 浏览: 152692 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Random和ThreadLocalRandom的实现原理

    博客分类:
  • java
阅读更多

         从JDK 7 开始引进了一个新的伪随机数生成器,ThreadLocalRandom,从名称可看出是一个与线程相关的Random,和之前的Random进行对比,ThreadLocalRandom在性能上和多线程并发处理上做了一些改进。

 

1,sun.misc.Unsafe

      由于在产生伪随机数过程中,Random和ThreadLocalRandom都使用到了一个特殊的包:sun.misc.Unsafe。先对此包作个简单介绍。

      sun.misc.Unsafe在JDK源代码中,有多处出现。为了更高效和更简单,它提供了一些与平台相关的更底层的功能,即提供了JNI的某些简单功能的替代,Unsafe的大部分方法都是Native方法。从名称也可以看出,它提供的是从Java意义上来说是“不安全”的一些功能,因此不应该在Java核心类库之外使用。实际上,除了通过反射机制,我们也无法获取到Unsafe的实例,使用它的功能。

       更具体的介绍,可参看:http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe。

 

2,理解Random:

       从Random的介绍中可看出,一个Random类的实例可用于生成一个伪随机数。生成伪随机数的过程,是指定一个seed,然后通过一个公式来生成一个新的seed,并且根据新的seed值得到伪随机数。而如果两个不同的Random实例,指定的seed相同,则生成的伪随机数也相同。

       代码展示: seed相同,则生成的随机数相同;

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		System.out.println("current seed:" + currentseed);
		Random r1 = new Random(currentseed);
		int value1 = r1.nextInt();
		
		Random r2 = new Random(currentseed);
		int value2 = r2.nextInt();
		
		System.out.println("Random Object: " + r1.hashCode() + " AND  " + r2.hashCode());
		
		System.out.println("get random int:" + value1 + "," + value2);
	}

   

    运行结果如下:

current seed:100
Random Object: 1311053135 AND  118352462
get random int:-1193959466,-1193959466

    通过运行结果可以看出,相同的seed,不同的Random对象,生成的随机数是一样的。

 

       

        在多个线程使用多个Random实例时,如果指定的seed是相同的,则生成的伪随机数序列也是相同的。因此需要使用不同的seed来创建Random实例,比如使用Random默认的seed,或使用当前的时间System.currentTimeMillis();

       代码展示:多线程中,seed相同,则生成的随机数相同;

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		System.out.println("current seed:" + currentseed);
		
		RandomThread rThread1 = new RandomThread(currentseed);
		new Thread(rThread1).start();
		
		RandomThread rThread2 = new RandomThread(currentseed);
		new Thread(rThread2).start();
		
}

public class RandomThread implements Runnable {

	private int seed;
	
	
	public RandomThread(int seed) {
		super();
		this.seed = seed;
	}

	public void run() {
		// TODO Auto-generated method stub
		Random r1 = new Random(seed);
		try {
			for(int k =0 ;k<5;k++){
				int value1 = r1.nextInt();
				System.out.println("get random int" + k + ":" + value1);
				Thread.sleep(200);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

     运行结果:

current seed:100
get random int0:-1193959466
get random int0:-1193959466
get random int1:-1139614796
get random int1:-1139614796
get random int2:837415749
get random int2:837415749
get random int3:-1220615319
get random int3:-1220615319
get random int4:-1429538713
get random int4:-1429538713

      在两个线程中,实例化两个Random,使用相同的seed,则产生了完全相同的两个随机数序列。

 

      从Random中获取随机数的源代码:

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));
    }
private static final long multiplier = 0x5DEECE66DL;
private static final long addend = 0xBL;
private static final long mask = (1L << 48) - 1;

 ,从源代码中可以看出,在生成随机数时,使用的公式:

nextseed = (oldseed * multiplier + addend) & mask;

     其中,oldseed则为传进去的初始seed。而其他三个参数,multiplier,addend和mask,都为类静态变量。因此即使是不同的Random实例,生成的nextseed值也是相同的,所得的的随机数也就是相同的。

 

 

       因此如果要确保随机数的效果,可以在多个线程中,使用同一个Random实例,这样就只需要维护同一套seed值。而Random实例生成随机数时对seed值的更新是一个原子操作,因此多个线程可能会在此产生阻塞而影响并发性能。

    获取随机数时的最后一行操作,更新seed值,是一个原子操作,AtomicLong类中直接通过调用unsafe.compareAndSwapLong方法完成。

private final AtomicLong seed;
//seed值设置成了final,当初始化seed值之后,再不能通过Java代码来修改seed值。
 
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));
    }

//seed.compareAndSet(oldseed, nextseed) 操作,调用unsafe的方法。
public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

其中 seed.compareAndSet(oldseed, nextseed),调用unsafe的方法来完成。该方法通过获得seed的内存位置偏移量,来设置seed的值,用nextseed值替换oldseed值。注意seed变量是设置成final的,因此无法通过Java程序再修改seed值,在此通过unsafe调用native方法来完成seed值的更新。

 

       代码展示多个线程共享同一个random实例时,从程序结果看,有一部分获取随机数的操作,花费的时间比较多,说明多线程使用同一Random时,容易产生阻塞:

        

public static void main(String[] args) {
		// TODO Auto-generated method stub
		int currentseed = 100;
		Random currRandom = new Random(currentseed);
		for(int k=0;k<20;k++){
			RandomThread rThread1 = new RandomThread(currRandom);
			new Thread(rThread1).start();	
		}
	}



public class RandomThread implements Runnable {

	private Random currentRandom;
	
	public RandomThread(Random random) {
		super();
		this.currentRandom = random;
	}

	public void run() {
		// TODO Auto-generated method stub
		try {
			for(int k =0 ;k<10;k++){
				long currenttime = System.currentTimeMillis();
				int value1 = currentRandom.nextInt(50);
				System.out.println("get random int" + k + ":" + value1);
				System.out.println("Cost time for random value:" + (System.currentTimeMillis() - currenttime));
				Thread.sleep(100 * value1);
			}
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 

 

       从以上的分析,可总结出Random存在的几个问题:

       生成多个实例,浪费资源;

       相同的seed值,产生的伪随机数序列相同,效果不理想;

       多个线程共享同一个实例时,会降低并发性能。

       因此ThreadLocalRandom应运而生。

 

3, 理解ThreadLocalRandom:

       针对Random存在的一些问题,ThreadLocalRandom有针对性的进行了改进。
       ThreadLocalRandom是单例模式,系统只保存一个ThreadLocalRandom实例。

       程序通过ThreadLocalRandom.current(); 获取ThreadLocalRandom实例。

public static ThreadLocalRandom current() {
        if (UNSAFE.getInt(Thread.currentThread(), PROBE) == 0)
            localInit();
        return instance;
    }
/**
     * Initialize Thread fields for the current thread.  Called only
     * when Thread.threadLocalRandomProbe is zero, indicating that a
     * thread local seed value needs to be generated. Note that even
     * though the initialization is purely thread-local, we need to
     * rely on (static) atomic generators to initialize the values.
     */
    static final void localInit() {
        int p = probeGenerator.addAndGet(PROBE_INCREMENT);
        int probe = (p == 0) ? 1 : p; // skip 0
        long seed = mix64(seeder.getAndAdd(SEEDER_INCREMENT));
        Thread t = Thread.currentThread();
        UNSAFE.putLong(t, SEED, seed);
        UNSAFE.putInt(t, PROBE, probe);
    }

         获取实例时,针对每个线程,只在第一次进行本地变量的参数初始化,多个线程的seed值也都是不同的。因此多个线程使用ThreadLocalRandom,获得的随机数也不一样。

        

       但是ThreadLocalRandom针对每一个线程,都保存一份变量值在本地内存,在第一次使用时,初始化各个变量值,并且赋值给每个变量。

       在更新seed值时,也都是在操作本地内存,避免了多线程操作时,对变量值的同步,大大提高了并发性能。

       我们可以看到,在ThreadLocalRandom中定义了一些变量,而这些变量的值,是通过Thread线程保存在本地,即每个线程都保存了一份变量:

    //ThreadLocalRandom中各变量的定义,以及通过Unsafe,获取保存在本地内存中的地址。
    // Unsafe mechanics
    private static final sun.misc.Unsafe UNSAFE;
    private static final long SEED;
    private static final long PROBE;
    private static final long SECONDARY;
    static {
        try {
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> tk = Thread.class;
            SEED = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSeed"));
            PROBE = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomProbe"));
            SECONDARY = UNSAFE.objectFieldOffset
                (tk.getDeclaredField("threadLocalRandomSecondarySeed"));
        } catch (Exception e) {
            throw new Error(e);
        }
    }

//Thread类中对每个变量的定义:
 // The following three initially uninitialized fields are exclusively
    // managed by class java.util.concurrent.ThreadLocalRandom. These
    // fields are used to build the high-performance PRNGs in the
    // concurrent code, and we can not risk accidental false sharing.
    // Hence, the fields are isolated with @Contended.

    /** The current seed for a ThreadLocalRandom */
    @sun.misc.Contended("tlr")
    long threadLocalRandomSeed;

    /** Probe hash value; nonzero if threadLocalRandomSeed initialized */
    @sun.misc.Contended("tlr")
    int threadLocalRandomProbe;

    /** Secondary seed isolated from public ThreadLocalRandom sequence */
    @sun.misc.Contended("tlr")
    int threadLocalRandomSecondarySeed;


//在ThreadLocalRandom中,是针对每个线程,进行seed的读取和更新。不存在多个线程共享的问题,因此也不存在影响并发性能的问题。
    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中,是针对每个线程,对该线程的变量seed进行读取和更新。不存在多个线程共享的问题,因此也不存在影响并发性能的问题。      

分享到:
评论

相关推荐

    实例讲解Java中random.nextInt()与Math.random()的基础用法

    在Java编程语言中,生成随机数是常见的任务,这通常涉及到`java.util.Random`类和`java.lang.Math`类中的`random()`方法。...理解这两个方法的工作原理和用法,可以帮助开发者更有效地在程序中引入随机性。

    java中随机输出1到32

    本文将深入探讨如何在Java中实现...通过以上分析,我们不仅理解了如何在Java中实现随机输出1到32的功能,还了解了Java中随机数生成的基本原理,以及如何优化和扩展代码,这对于提升代码质量和功能多样性具有重要意义。

    random-algorithms

    此外,`java.util.concurrent.ThreadLocalRandom`和`java.security.SecureRandom`分别提供了线程安全的随机数生成和更安全的随机数生成,适用于加密和其他安全性要求高的场景。 项目中的具体代码可能涵盖了各种随机...

    java 随机数的资源

    在Java编程语言中,随机数的应用非常广泛,可以用于各种模拟、游戏开发、加密算法以及数据分析等场景。...在实际应用中,理解这些随机数生成器的工作原理和使用技巧,将有助于编写出更加高效和可靠的代码。

    java 抽奖活动的资源

    在Java中,我们通常会使用`Random`类或`ThreadLocalRandom`类来生成随机数,以确保每次抽奖的中奖概率均匀分布。 抽奖活动的实现方式多种多样,一种常见的方法是通过创建一个包含所有参与者的列表,然后随机选取一...

    摇号算法实现

    本文将详细解析摇号算法的核心概念、实现原理以及常见问题。 摇号算法的核心目标是保证随机性与公平性。随机性意味着每一个参与者被选中的概率相同,而公平性则要求算法不能被人为操控,确保结果的不可预测性。在...

    ZhuanpainView-抽奖轮盘Handler view实现可设定每次抽中位置.zip

    4. **随机数生成**: 抽奖结果的不确定性需要通过随机数生成器实现,Java的`Random`类或者Android的`java.util.concurrent.ThreadLocalRandom`都可以用来生成随机的中奖位置。 5. **布局管理**: 项目可能包含了XML...

    random-between-num

    在这个话题下,我们将深入探讨如何使用Java的标准库`java.util.Random`类来生成随机数,并了解其工作原理。 `Random`类是Java提供的核心类,位于`java.util`包中,它提供了一系列方法来生成不同类型的随机数,如...

    蓝桥杯练习题目和结果代码.zip

    这需要理解和应用基本的数学原理和算法。 7. **分解质因数.java**:分解质因数是将一个合数写成几个质数的乘积。这个题目可能要求设计一个算法来分解输入的整数,需要对质数和因数分解有深入理解。 8. **瓷砖铺放....

    java伪随机数

    Java中的伪随机数生成主要通过java.util.Random类实现。这个类使用线性同余生成算法来产生伪随机数。下面是Random类中生成伪随机数的关键知识点: 1. 构造函数:Random类提供了无参构造函数和带种子的构造函数。...

    javarandom源码-PrasannaJavaChallenge:包含Prime随机发生器,Randomizer和某些任务的源代码

    此外,`Random`类支持线程安全的实例化,如果需要在多线程环境下使用,可以通过`ThreadLocalRandom`类来获取线程局部的`Random`实例,避免了同步带来的性能开销。 在PrasannaJavaChallenge项目中,作者进一步扩展了...

    打地鼠游戏——JAVA大作业。.zip

    5. **随机数生成**:地鼠的出现位置需要随机生成,这需要Java的`Random`类或者`ThreadLocalRandom`类来实现,以确保每次游戏的体验都有所不同。 6. **图形用户界面GUI**:游戏界面可能使用Java的AWT或Swing库创建,...

    number(web).rar_猜数字java web

    3. **随机数生成**:为了设置需要用户猜的数字,项目中会用到Java的`Random`类或者`ThreadLocalRandom`类来生成随机数。 4. **用户输入验证**:在用户提交猜测时,需要检查输入是否为有效数字,这可以通过正则...

    34丨 实战一(上):通过一段ID生成器代码,学习如何发现代码质量问题1

    例如,可以添加注释来解释代码的目的和工作原理,帮助其他开发者理解。 2. **异常处理**:获取本地主机名可能会抛出`UnknownHostException`,但在示例代码中没有进行捕获和处理。应当考虑添加异常处理机制,避免...

    java_roulette.rar_Roulette_java_摇奖模拟程序_模拟摇奖

    1. **随机数生成**:摇奖的核心是生成随机数字,Java提供了Random类或者更高级的ThreadLocalRandom和SecureRandom类来生成随机数。开发者可能根据摇奖规则设定一个特定范围,然后在这个范围内生成随机数。 2. **...

    java课程设计——烟花

    Java的`Random`类或者`ThreadLocalRandom`类可以用来生成这些随机值。 8. **数据结构和算法**:可能使用数组、链表或其他数据结构来存储和管理烟花的状态。同时,优化算法如搜索、排序或缓存策略可能被用来提高性能...

    20240526.pdf

    1. **生成随机数**:使用Random类或者ThreadLocalRandom类生成指定范围内的随机数。 2. **添加到集合**:将生成的随机数添加到集合中,如HashSet或TreeSet。 3. **去重与排序**:由于HashSet不会重复添加元素,而...

    java 双色球项目

    2. `BallGenerator.java` - 用于生成红球和蓝球的随机数类,可能使用了Java的`Random`类或`ThreadLocalRandom`类。 3. `LotteryTicket.java` - 投注票类,存储用户选择的红球和蓝球号码,以及投注相关信息。 4. `...

    android仿大转盘 的完整项目 带音效

    开发者可能使用`Random`类或`java.util.concurrent.ThreadLocalRandom`类来生成随机数,并根据奖品位置对应的区间来确定最终结果。 5. **音效播放**: 音效的添加增强了用户体验。Android的`MediaPlayer`类或`...

    Android应用源码之转盘抽奖实例.rar

    7. **随机数生成**:抽奖结果的随机性需要通过`Random`类或者更高级的`ThreadLocalRandom`来实现,理解随机数生成的原理和用法。 8. **多线程处理**:如果抽奖过程中涉及网络请求或较耗时的操作,可能需要使用`...

Global site tag (gtag.js) - Google Analytics