在兲朝游戏开发中,经常出现抽奖形式坑人的情况,这需要用到概率。然而java api所自带的是伪随机的接口,随机后几率并不均匀。这就出现了抽奖掉落概率未如理想的状况。为了使概率更均匀,将范围内全数字随机可以达到目的。当然,将全数字映射出来需要更高的内存消耗。更大的消耗是因为总想针对某个活动得到更均匀的概率,而不是整个框架都一个全数字数组来随机使用,因为这将使随机结构偏离原有的单个设定。
先来一种安全的简单全数字随机。将全数组字在数组之内打乱,然后通过不断缩小随机范围在数组之内随机后交换,确保在size次随机后,所有数字都被随机了一遍。
import java.util.Random; public class ExactRandom { /** * 随机数字总数 */ private final int size; /** * 全数字数组 */ private final int[] arr; /** * 随机长度下限 */ private int bound; public Random r = new Random(); public ExactRandom(int size,int offset) { super(); this.size = size; arr = new int[size]; for(int i=0; i<size; i++){ arr[i] = i+offset; } for(int i=0; i<size; i++){ int pos = r.nextInt(size); int temp = arr[i]; arr[i] = arr[pos]; arr[pos] = temp; } bound = size; } /** * 更大的随机集群使用 * @param arr */ public ExactRandom(int[] arr) { super(); this.arr = arr; this.size = arr.length; this.bound = size; } public synchronized int getRandonNum(){ int pos = r.nextInt(bound--); int temp = arr[pos]; arr[pos] = arr[bound]; arr[bound] = temp; if(bound <= 0){ bound = size; } return temp; } public int[] getArr() { return arr; } }
为了保证在取随机数过程中数组内的数字交换不会引起竞争问题,使用了同步锁保证安全性。然而,在多用户多线程调用下很容易因锁竞争而使得效率低下。为此,将参照ConcurrentHashMap的分段锁思想,将随机数组切割成许多小块,以期将锁限制在自旋锁、轻量级锁的范围内,大幅减少重量级锁出现的几率。通过使用一个AtomicInteger,使在相邻两次的取随机数调用中命中不同的随机数组,减低同时命中同一ExactRandom所引起的锁升级。当lockSize数值越大时,锁竞争几率越小。
import java.util.Arrays; import java.util.concurrent.atomic.AtomicInteger; public class HyperExactRandom { private final int size; private final int offset; private final int lockSize; private final ExactRandom[] rans; private AtomicInteger lockPos = new AtomicInteger(); public HyperExactRandom(int size,int lockSize, int offset) { super(); this.size = size; this.offset = offset; this.lockSize = lockSize; int patition = size/lockSize; int lastPartition = patition + size%lockSize; rans = new ExactRandom[lockSize]; ExactRandom temp = new ExactRandom(size, offset); int[] fullArr = temp.getArr(); temp = null; for(int i=0,lastPos = lockSize -1; i<lockSize; i++){ int from = i*patition; int to = i != lastPos ? from+patition : from+lastPartition; rans[i] = new ExactRandom(Arrays.copyOfRange(fullArr, i*patition, to)); } } public int getRandom(){ int pos = lockPos.getAndIncrement(); if(pos > 0){ pos %= lockSize; }else if(pos < 0){ long t = 0xFFFFFFFFL; t = t&pos % lockSize; pos = (int)t; } return rans[pos].getRandonNum(); } public int getSize() { return size; } public int getOffset() { return offset; } // public static void main(String[] args) { // int fullSize = 1000000; // HyperExactRandom ran = new HyperExactRandom(fullSize, 1000, 1); //// HashSet<Integer> set = new HashSet<>(2*fullSize); // long sum = 0; // for(int i=0; i<fullSize;i++){ // sum += ran.getRandom(); // } // // long sum1 = 0; // for(int i=0; i<10*fullSize;i++){ // sum1 += ran.getRandom(); // if(i % fullSize == 0 && i != 0){ // System.out.println("rest ="+ (sum1%sum)); // } // } // // System.out.println(sum); // } }