You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java中同步getter与run方法解决主线程未等待子线程问题的原理疑问

问题解析与正确解决方案

原问题的核心原因

原代码里的主线程在启动所有子线程后,直接调用getMax()获取结果,完全没等子线程执行完毕。这时候子线程可能还在计算过程中,max变量还是类初始化时的默认值0,拿到的结果自然不对。

为什么加synchronized能“凑效”?

给run()和getMax()都加synchronized后,这两个方法会竞争同一个对象锁:

  1. 子线程启动后进入run()方法,会持有当前MaxTask对象的锁,直到run()执行完毕才释放。
  2. 主线程调用getMax()时,发现锁被子线程占用,会进入阻塞状态,必须等到子线程把run()跑完、释放锁后,才能进入getMax()拿到正确的计算结果。
  3. 另外,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 09:21:35