做游戏后台的,就避免不了生成随机数,随机物品给玩家,就会存在大量的调用随机方法的可能。我们探讨下哪种写法比较合适。
java.util.Random 从Java 1.0开始就存在了。它是一个线程安全类,理论上可以通过它同时在多个线程中获得互不相同的随机数。这样的线程安全是通过AtomicLong实现的。
Random 使用 AtomicLong CAS (compare-and-set)操作来更新它的seed,尽管很多非阻塞式算法中使用了非阻塞式原语,CAS在资源高度竞争时的表现依然糟糕。我们写几个对比测试下。
测试代码:
private static class RandomTask implements Runnable
{
private final Random rnd;
protected final int id;
private final long count;
private final CountDownLatch latch;
private RandomTask(Random rnd, int id, long count, CountDownLatch latch) {
this.rnd = rnd;
this.id = id;
this.count = count;
this.latch = latch;
}
protected Random getRandom()
{
return rnd;
}
@Override
public void run() {
try {
final Random r = getRandom();
latch.countDown();
latch.await();
final long start = System.currentTimeMillis();
int sum = 0;
for (long j = 0; j < count; ++j )
{
sum += r.nextInt();
}
final long time = System.currentTimeMillis() - start;
System.out.println( "Thread #" + id + " Time = " + time / 1000.0 + " sec, sum = " + sum );
} catch (InterruptedException e) {
}
}
}
private static final long COUNT = 10000000;
private static final int THREADS = 2;
public static void main(String[] args) {
System.out.println( "Random start" );
testRandom(THREADS, COUNT);
// System.out.println("ThreadLocal");
// testTL_Random(THREADS, COUNT);
// System.out.println("ThreadLocalRandom");
// testTLRandom(THREADS, COUNT);
// System.out.println(" Random[] with no padding");
// testRandomArray(THREADS, COUNT, 1);
// System.out.println(" Random[] with padding");
// testRandomArray(THREADS, COUNT, 2);
}
private static void testRandom( final int threads, final long cnt )
{
final CountDownLatch latch = new CountDownLatch( threads );
final Random r = new Random( 100 );
for ( int i = 0; i < threads; ++i )
{
final Thread thread = new Thread( new RandomTask( r, i, cnt, latch ) );
thread.start();
}
}
THREADS = 2 时:
Random start
Thread #1 Time = 0.429 sec, sum = -1213943545
Thread #0 Time = 0.453 sec, sum = 1316120331
THREADS = 4 时:
Random start
Thread #3 Time = 0.945 sec, sum = 637064032
Thread #1 Time = 1.159 sec, sum = -599415297
Thread #2 Time = 1.24 sec, sum = 912228943
Thread #0 Time = 1.3 sec, sum = -1598737659
简单的测试下,竞争越激烈,运行时间越长。
private static void testRandomArray( final int threads, final long cnt, final int padding )
{
final CountDownLatch latch = new CountDownLatch( threads );
final Random[] rnd = new Random[threads * padding];
for ( int i = 0; i < threads * padding; ++i ) //allocate together
rnd[ i ] = new Random( 100 );
for ( int i = 0; i < threads; ++i )
{
final Thread thread = new Thread( new RandomTask( rnd[ i * padding ], i, cnt, latch ) );
thread.start();
}
}
参数padding 来控制数组缓存的大小。
THREADS = 4 时:
Random start
Random[] with no padding
Thread #0 Time = 0.55 sec, sum = 2024089153
Thread #3 Time = 0.611 sec, sum = 2024089153
Thread #1 Time = 0.617 sec, sum = 2024089153
Thread #2 Time = 0.671 sec, sum = 2024089153
Random[] with padding
Thread #1 Time = 0.411 sec, sum = 2024089153
Thread #0 Time = 0.419 sec, sum = 2024089153
Thread #3 Time = 0.505 sec, sum = 2024089153
Thread #2 Time = 0.455 sec, sum = 2024089153
在程序运行的线程数低于CPU的线程数时性能没有下降,当程序运行的线程数超过CPU的线程数时性能才线性的降低。另一个要注意的重点是,单一线程执行的效率没有单实例Random高——无竞争的CAS操作仍然表现糟糕。
THREADS = 4 时:
Random start
ThreadLocalRandom
Thread #3 Time = 0.029 sec, sum = -1502647470
Thread #0 Time = 0.043 sec, sum = -435913192
Thread #2 Time = 0.023 sec, sum = 1787470510
Thread #1 Time = 0.087 sec, sum = 683332778
相比较前面几个例子,多线程竞争下,性能提升明显。
private static void testTL_Random( final int threads, final long cnt )
{
final CountDownLatch latch = new CountDownLatch( threads );
final ThreadLocal rnd = new ThreadLocal() {
@Override
protected Random initialValue() {
return new Random( 100 );
}
};
for ( int i = 0; i < threads; ++i )
{
final Thread thread = new Thread( new RandomTask( null, i, cnt, latch ) {
@Override
protected Random getRandom() {
return rnd.get();
}
} );
thread.start();
}
}
THREADS = 4 时:
Random start
ThreadLocal
Thread #1 Time = 0.402 sec, sum = 2024089153
Thread #2 Time = 0.374 sec, sum = 2024089153
Thread #0 Time = 0.448 sec, sum = 2024089153
Thread #3 Time = 0.458 sec, sum = 2024089153
把java.util.Random实例装入ThreadLocal后执行的效率有些不一样,当线程数超过CPU核心数时性能会有个下降。
本人能力有限,只能通过测试作对比,来看出之间的区别,为啥造成这样就要深入理解了。