Java中同步getter与run方法解决主线程未等待子线程问题的原理疑问
问题解析与正确解决方案
原问题的核心原因
原代码里的主线程在启动所有子线程后,直接调用getMax()获取结果,完全没等子线程执行完毕。这时候子线程可能还在计算过程中,max变量还是类初始化时的默认值0,拿到的结果自然不对。
为什么加synchronized能“凑效”?
给run()和getMax()都加synchronized后,这两个方法会竞争同一个对象锁:
- 子线程启动后进入
run()方法,会持有当前MaxTask对象的锁,直到run()执行完毕才释放。 - 主线程调用
getMax()时,发现锁被子线程占用,会进入阻塞状态,必须等到子线程把run()跑完、释放锁后,才能进入getMax()拿到正确的计算结果。 - 另外,
synchronized还保证了内存可见性:子线程修改的max值会同步到主内存,主线程读到的是最新值,而非缓存的旧数据。
但这纯属歪打正着,run()方法加synchronized完全没必要,而且靠锁的阻塞来实现等待,逻辑上不直观,还可能带来额外的性能开销,不是正确的解决思路。
正确的解决方式:显式等待子线程完成
最直接规范的做法是用Thread.join(),让主线程等待每个子线程执行完毕后再收集结果:
修改后的MainMax代码
public class MainMax { public static void main(String[] args) throws Exception { int size = 100; int workers = 10; int[] arr = new int[size]; int max = 0; for (int i = 0; i < size; i++) { arr[i] = (int)(Math.random() * 100); if (max < arr[i]) max = arr[i]; } System.out.println("max=" + max); int gsize = (arr.length - 1) / workers; MaxTask[] tasks = new MaxTask[workers]; int first = 0; int last; for (int i = 0; i < workers; i++) { last = first + gsize; // 处理最后一个线程的边界,避免数组越界 if (i == workers - 1) { last = arr.length - 1; } tasks[i] = new MaxTask(arr, first, last); tasks[i].start(); first = last + 1; } // 关键步骤:等待所有子线程执行完毕 for (MaxTask task : tasks) { task.join(); } int maxmax = tasks[0].getMax(); int temp; for (int i = 1; i < workers; i++) { temp = tasks[i].getMax(); if (temp > maxmax) maxmax = temp; } System.out.println("maxmax=" + maxmax); } }
更优雅的并发工具方案
用ExecutorService结合Callable能更灵活地管理线程和获取结果:
import java.util.ArrayList; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class MainMax { public static void main(String[] args) throws Exception { int size = 100; int workers = 10; int[] arr = new int[size]; int expectedMax = 0; for (int i = 0; i < size; i++) { arr[i] = (int)(Math.random() * 100); if (expectedMax < arr[i]) expectedMax = arr[i]; } System.out.println("expected max=" + expectedMax); ExecutorService executor = Executors.newFixedThreadPool(workers); List<Callable<Integer>> tasks = new ArrayList<>(); int gsize = (arr.length - 1) / workers; int first = 0; for (int i = 0; i < workers; i++) { int last = first + gsize; if (i == workers - 1) { last = arr.length - 1; } final int start = first; final int end = last; tasks.add(() -> { int subMax = arr[start]; for (int j = start + 1; j <= end; j++) { if (arr[j] > subMax) subMax = arr[j]; } return subMax; }); first = last + 1; } // 获取所有子任务的结果 List<Future<Integer>> results = executor.invokeAll(tasks); int actualMax = Integer.MIN_VALUE; for (Future<Integer> future : results) { int subMax = future.get(); if (subMax > actualMax) actualMax = subMax; } System.out.println("actual max=" + actualMax); executor.shutdown(); } }
总结
- 原问题本质是主线程未等待子线程完成就读取结果。
- 加
synchronized是巧合解决,不是正确思路。 - 规范做法是用
Thread.join()显式等待,或使用ExecutorService这类专业并发工具。
内容的提问来源于stack exchange,提问作者GuyO
相关产品推荐
相关产品推荐

