Java多线程场景下如何安全访问并写入类的ArrayList存储素数?
核心问题排查
- 区间拆分逻辑完全错误:你当前给所有
MyRunnable传入的计算区间都是固定的d-numThreads到d,根本没有按线程拆分不同的计算区间,和你预期的多线程分工完全不符,这也是你输出结果异常的直接原因。 - 线程创建逻辑错误:不需要遍历
[lower, upper)判断i%d==0来创建线程,直接按指定线程数均分区间即可。 - 冗余锁逻辑:
isPrime是无共享状态的纯计算方法,加synchronized会导致所有线程串行执行素数判断,完全丧失多线程性能优势- 你已经用
Collections.synchronizedList包装了ArrayList,其add方法本身线程安全,不需要额外加同步块 - 主类和
MyRunnable中各定义了一个独立的lock对象,无实际作用
- 集合初始化遗漏:
threadsArrayList没有实例化,运行会抛出空指针异常
修正方案
1. 修正区间拆分与线程创建逻辑
按线程数均分计算区间,每个线程分配独立的计算段,最后一个线程兜底处理边界余数。
2. 清理冗余同步逻辑
删除不需要的锁代码,提升并发效率。
修正后参考代码
import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Primes { int numThreads; List<Integer> primes = Collections.synchronizedList(new ArrayList<>()); public ArrayList<Thread> threads = new ArrayList<>(); // 初始化线程集合 public Primes(int numThreads) { this.numThreads = numThreads; } public Primes findPrimes(int lower, int upper) { int range = upper - lower; int step = range / numThreads; // 按线程数拆分区间 for (int k = 0; k < numThreads; k++) { int threadLower = lower + k * step; int threadUpper = (k == numThreads - 1) ? upper : lower + (k + 1) * step; threads.add(new Thread(new MyRunnable(threadLower, threadUpper, primes))); } for(Thread t : threads) t.start(); for(Thread t : threads) { try { t.join(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } return this; } public static class MyRunnable implements Runnable { private int lower; private int upper; List<Integer> prime; public MyRunnable(int lower, int upper, List<Integer> prime) { this.lower = lower; this.upper = upper; this.prime = prime; } // 去掉synchronized,纯计算无需同步 protected boolean isPrime(int number) { if (number <= 1) return false; // 优化判断条件到sqrt(number),提升性能 for (int i = 2; i <= Math.sqrt(number); i++) { if (number % i == 0) return false; } return true; } public void run() { for (int i=lower; i<upper; i++) { if (isPrime(i)){ prime.add(i); // synchronizedList的add自带线程安全,无需额外加锁 } } } } // 测试用例示例 public static void main(String[] args) { int threadNum = Integer.parseInt(args[0]); Primes primes = new Primes(threadNum); primes.findPrimes(0, 1000000); System.out.printf("0到1000000范围内共有%d个素数,总和为%d%n", primes.primes.size(), primes.primes.stream().mapToInt(Integer::intValue).sum() ); } }
额外优化建议
- 素数判断逻辑优化为循环到
Math.sqrt(number)而非number/2,计算效率会提升数倍 - 批量收集子线程的素数结果后再合并到主集合,可以减少同步操作的开销,适合大计算量场景
内容的提问来源于stack exchange,提问作者demo
相关产品推荐
相关产品推荐

