`
fly_ever
  • 浏览: 149919 次
  • 性别: 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进行读取和更新。不存在多个线程共享的问题,因此也不存在影响并发性能的问题。      

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics