抢红包问题
许多人看到这个问题的第一反应是怎么分配红包,用0~1随机数或是X-Y(0,1)正态分布随机数,力图使红包分配更合理。但忽略了一个非常重要的字眼–“抢”,所以这个问题主要考虑的是并发编程。
获得正态分布随机数方式
1.Random中得nextGaussian()方法,是X-Y(0,1)标准正态分布随机数
2 独立同分布的中心极限定理
3.Box–Muller算法
代码
import java.text.NumberFormat;
import java.util.Arrays;
import java.util.Random;
public class RedEnvelopes {
private double MAX=1;
private double MIN=0.4;
private int people;
private int stay;
private static final Random rand = new Random();
private int balance;
private int total;
private double[] data;
public RedEnvelopes(double total,int people) {
this.people=people;
this.stay=this.people;
/*
将total乘以100取整避免浮点运算带来的误差
*/
this.total=(int)(total*100);
this.balance=this.total;
createData();
System.out.println("RedEnvelopes data:"+Arrays.toString(data) );
double sum=0;
for(int i=0;i<data.length;i++) {
sum+=data[i];
}
System.out.println("data sum :"+sum);
}
public void createData() {
data=new double[people];
for(int i=people-1;i>=0;i--) {
if(i==0) {
data[i]=1.0*balance/100;
return;
}
int mean=balance/(i+1);
double tmp=0;
do {
tmp=Math.abs(rand.nextGaussian());
}while(tmp<MIN||tmp>MAX);
int ret=(int) (tmp*mean);
balance-=ret;
data[i]=1.0*ret/100;
}
}
//返回实际金额
public double get() throws Exception {
if(stay>0) {
--stay;
return data[stay];
}
else
throw new Exception("Error");
}
}
测试
public static void main(String[] args) {
RedEnvelopes red=new RedEnvelopes(100,10);
}
输出
RedEnvelopes data:[24.27, 11.07, 14.54, 14.49, 9.31, 7.12, 5.35, 4.71, 4.5, 4.64]
data sum :100.0
(sum的值由于浮点运算带来的误差可能不为100,但data的和必为100)
添加两个线程
public static void main(String[] args) {
RedEnvelopes red=new RedEnvelopes(100,10);
Integer i=10;
Runnable r1=new Runnable() {
public void run() {
for(int i=0;i<5;i++) {
try {
System.out.println("Runnable1:"+red.get());
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
Runnable r2=new Runnable() {
public void run() {
for(int i=0;i<5;i++) {
try {
System.out.println("Runnable2:"+red.get());
}catch (Exception e) {
e.printStackTrace();
}
}
}
};
Thread t1 =new Thread(r1);
t1.start();
Thread t2 = new Thread(r2);
t2.start();
}
输出
浮点运算带来的误差显而易见,所以转换为整型是必要的。
两个线程同时去取钱看样子没问题。
放大时延
public double get() throws Exception {
if(stay>0) {
--stay;
Thread.sleep(1000);
return data[stay];
}
else
throw new Exception("Error");
}
输出
时延变大后问题凸显了出来。
解决办法
用 synchronized修饰get()方法,避免并发访问
public synchronized double get() throws Exception {
if(stay>0) {
--stay;
Thread.sleep(1000);
return data[stay];
}
else
throw new Exception("Error");
}
输出
对于抢红包问题,显然这样解决方式是比较正确。