Java使用多线程生成指定范围唯一随机数的问题及方案咨询
问题根因
你当前代码存在3个核心问题:
join()用法错误:你先启动thread1,调用join()等待thread1完全执行结束后才启动thread2,本质是串行执行,完全没有发挥多线程的作用。- 共享资源操作无同步:
ArrayList本身不是线程安全的,你对List的contains()校验和remove()删除两个操作不是原子性的。即便你调整join()逻辑让多线程并发执行,也会出现两个线程同时校验到某个值存在,第一个线程删除成功后,第二个线程再执行删除时数值已经不存在,自然不会打印第二条输出,这就是你偶现缺结果的核心原因。 - 执行逻辑有漏洞:
run()方法中仅生成一次随机数,如果值不存在就直接break退出,不会重新生成新的随机数尝试获取可用值,直接结束线程。
可行实现方案
方案1:加同步锁保证操作原子性,修复执行逻辑
给共享列表的检查、删除操作加同步锁,同时修改run()方法的逻辑,没有拿到有效随机数就循环生成直到获取成功:
public class randomThreadGen implements Runnable { private int lowerBound; private int upperBound; private final ArrayList<Integer> list = new ArrayList<>(); private final Object lock = new Object(); public randomThreadGen(int lowerb, int upperb) { this.lowerBound = lowerb; this.upperBound = upperb; for (int i = lowerb; i <= upperb; i++) { list.add(i); } } @Override public void run() { Integer val = null; // 循环直到拿到可用数值 while (val == null) { int randomVal = ThreadLocalRandom.current().nextInt(lowerBound, upperBound + 1); // 检查+删除操作加锁,保证原子性 synchronized (lock) { if (list.contains(randomVal)) { list.remove(Integer.valueOf(randomVal)); val = randomVal; } } } System.out.println("Random value for " + Thread.currentThread().getName() + " " + val); System.out.println("List values: " + list); } }
调整Main类逻辑,不要用join()串行执行,直接同时启动所有线程:
public class Main { public static void main(String[] args) { randomThreadGen randomRange = new randomThreadGen(1, 2); Thread thread1 = new Thread(randomRange); Thread thread2 = new Thread(randomRange); thread1.start(); thread2.start(); } }
方案2:使用线程安全集合
直接用JDK提供的线程安全集合如CopyOnWriteArrayList、ConcurrentSkipListSet替代ArrayList,可以省去手动加锁的步骤,同时保证操作的线程安全性。
方案3:预洗牌队列方案(性能更优)
如果是范围N对应启动N个线程的场景,可以提前把范围内的所有数字打乱后存入线程安全队列,每个线程直接从队列取一个元素即可,不需要每次生成随机数再校验是否存在,执行效率更高:
public class randomThreadGen implements Runnable { private final ConcurrentLinkedQueue<Integer> queue; public randomThreadGen(int lowerb, int upperb) { List<Integer> list = IntStream.rangeClosed(lowerb, upperb) .boxed() .collect(Collectors.toList()); Collections.shuffle(list); queue = new ConcurrentLinkedQueue<>(list); } @Override public void run() { Integer val = queue.poll(); System.out.println("Random value for " + Thread.currentThread().getName() + " " + val); } }
内容的提问来源于stack exchange,提问作者tom
相关产品推荐
相关产品推荐

