欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Random和ThreadLocalRandom的实现原理

程序员文章站 2022-04-10 22:34:09
...

         从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并发锁机制

下一篇: Java 并发