使用多线程同时访问同一个Random实例发现的耗时问题,求解答

cangwuwuwu 学生  2020-09-06 04:42:23
本人半小白。在学习《实战Java高并发程序设计》这本书时,跟着书写了一个例子,以下是代码。
在执行 代码 49~56行时,每个线程的执行时间在3000ms+,总执行时间 10000ms左右
本着简化代码的强迫症,把代码改成77~80行这个样子,但是每个线程执行时间变成了100ms左右,总执行时间400ms+。请问这是由于什么原因造成的呢?是简写的代码有问题吗?还是jdk(8)有相关优化?求赐教谢谢

public class RandomTimeTest {
// 要产生的随机数数量
public static final int GEN_COUNT = 10000000;
// 参与工作的线程数量
public static final int THREAD_COUNT = 4;
// 定义线程池
static ExecutorService exe = Executors.newFixedThreadPool(THREAD_COUNT);

public static Random rnd = new Random(123);
public static ThreadLocalRandom tlRnd = ThreadLocalRandom.current();

// 由ThreadLocal封装的Random
public static ThreadLocal<Random> tRnd = ThreadLocal.withInitial(() -> new Random(123));

public static class RndTask implements Callable<Long> {
private int mode = 0;

public RndTask(int mode) {
this.mode = mode;
}

// 创建随机数的方式
public Random getRandom() {
if (mode == 0)
return rnd;
else if (mode == 1)
return tRnd.get();
else if (mode == 2)
return tlRnd;
else
return null;
}

/**
* 每个线程产生若干随机数,完成工作后记录幷返回所消耗的时间
*/
@Override
public Long call() throws Exception {
long b = System.currentTimeMillis();
for (int i = 0; i < GEN_COUNT; i++)
getRandom().nextInt();
long e = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+ " spend " + (e - b) + "ms");
return e - b;
}
}

public static void main(String[] args) throws ExecutionException, InterruptedException {
Future<Long>[] futures = new Future[THREAD_COUNT];
// 多线程共享Random实例
for (int i = 0; i < THREAD_COUNT; i++)
futures[i] = exe.submit(new RndTask(0));
long totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += futures[i].get();
System.out.println("多线程同时访问同一个Random实例:" + totaltime + "ms");


// 使用ThreadLocal情况
for (int i = 0; i < THREAD_COUNT; i++)
futures[i] = exe.submit(new RndTask(1));
totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += futures[i].get();
System.out.println("使用ThreadLocal包装Random实例: " + totaltime + "ms");

// 使用ThreadLocalRandom情况
for (int i = 0; i < THREAD_COUNT; i++)
futures[i] = exe.submit(new RndTask(2));
totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += futures[i].get();
System.out.println("使用ThreadLocalRandom实例: " + totaltime + "ms");
exe.shutdown();


/*long totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += exe.submit(new RndTask(0)).get();
System.out.println("多线程同时访问同一个Random实例:" + totaltime + "ms");

totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += exe.submit(new RndTask(1)).get();
System.out.println("使用ThreadLocal包装Random实例:" + totaltime + "ms");

totaltime = 0;
for (int i = 0; i < THREAD_COUNT; i++)
totaltime += exe.submit(new RndTask(2)).get();
System.out.println("使用ThreadLocalRandom实例:" + totaltime + "ms");
exe.shutdown();*/
}
}
...全文
2472 点赞 收藏 6
写回复
6 条回复
cangwuwuwu 2020年09月08日
引用 5 楼 冰思雨 的回复:
然后,解释一下楼主的问题。 Random 的线程安全,采用的是 CAS 操作 。 这种操作,在线程频繁竞争的时候,效率反而会降低。 竞争越激烈,效率越低下,因为每次compare总是不相当,CPU的指令都浪费了,当然它是没有阻塞的原语操作。 能理解吗?就是那种疯狂的调用next函数,生成随机数的操作,所产生的问题。 如果多线程共用同一个Random对象,那么,线程数最好不要超过CPU的核心数,超过的数量越大,竞争越激烈,效率越低。 改用 ThreadLocal 比较好,不用担心线程间的竞争了,但是,线程数超过CPU核心数后,效率依然会有所降低,但是,要比共用的情况强的太多了。
我明白了 非常感谢!! 另外,非常不好意思,我结贴分给错楼层了
回复 点赞
cangwuwuwu 2020年09月07日
引用 1 楼 KeepSayingNo的回复:
感觉这个代码写得比较冗杂,如果是用多线程产生随机数,应该没有必要这样去写
感谢回复!我觉得书中是把这个当做一个例子帮助读者理解,或许是有些冗杂了。
回复 点赞
KeepSayingNo 2020年09月07日
感觉这个代码写得比较冗杂,如果是用多线程产生随机数,应该没有必要这样去写
回复 点赞
冰思雨 2020年09月07日
然后,解释一下楼主的问题。 Random 的线程安全,采用的是 CAS 操作 。 这种操作,在线程频繁竞争的时候,效率反而会降低。 竞争越激烈,效率越低下,因为每次compare总是不相当,CPU的指令都浪费了,当然它是没有阻塞的原语操作。 能理解吗?就是那种疯狂的调用next函数,生成随机数的操作,所产生的问题。 如果多线程共用同一个Random对象,那么,线程数最好不要超过CPU的核心数,超过的数量越大,竞争越激烈,效率越低。 改用 ThreadLocal 比较好,不用担心线程间的竞争了,但是,线程数超过CPU核心数后,效率依然会有所降低,但是,要比共用的情况强的太多了。
回复 点赞
长江水面写日记 2020年09月07日
你简化代码反倒是没能使多线程发挥作用,,主线程一直在阻塞着挨个获取结果.
回复 点赞
冰思雨 2020年09月07日
你这77~91行的代码,和单线程跑一遍,有啥区别?
回复 点赞
发动态
发帖子
Java SE
创建于2007-09-28

3.4w+

社区成员

30.7w+

社区内容

Java 2 Standard Edition
社区公告
暂无公告