使用join()等待线程执行却未得到预期结果的问题
问题分析与解决:join()后count值不符合预期的原因
核心误区
你以为调用t1.join()会让t2等t1执行完再启动,但实际上t1.start()和t2.start()是连续执行的——两个线程几乎同时开始运行。t1.join()只是让主线程等待t1执行完毕,但这时候t2早已经在后台执行count++操作了,所以第一个System.out.println(count)打印的是t1跑完的10000加上t2已经执行了一部分的结果,自然不是你预期的10000。
第二个问题:count++的原子性
就算你让线程按顺序启动,count++本身也不是原子操作——它拆分为三个步骤:
- 读取当前
count的值 - 对值加1
- 将新值写回
count
哪怕加了volatile,也只能保证线程间的可见性,无法避免多个线程同时执行这三步时出现竞态条件:比如两个线程同时读到同一个值,加1后写回,相当于只加了1次,最终count会小于20000。
两种修正方案
方案1:让线程真正按顺序启动
如果要让t1完全执行完再启动t2,把t2.start()放到t1.join()之后:
public class TestJoinMethod { volatile private int count =0; public static void main(String[] args) { TestJoinMethod testJoinMethod = new TestJoinMethod(); testJoinMethod.execute(); } public void execute() { Thread t1 = new Thread(new Runnable() { @Override public void run() { for(int i=0; i<10000; i++) { count++; } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for(int i=0; i<10000; i++) { count++; } } }); t1.start(); try { t1.join(); System.out.println(count); // 此时输出10000 t2.start(); t2.join(); System.out.println(count); // 此时输出20000 } catch (InterruptedException e) { throw new RuntimeException(e); } } }
方案2:用原子类保证count++的原子性
如果需要两个线程同时运行,但保证count的正确累加,可以用java.util.concurrent.atomic.AtomicInteger替换普通int,它的incrementAndGet()方法是原子操作:
import java.util.concurrent.atomic.AtomicInteger; public class TestJoinMethod { private AtomicInteger count = new AtomicInteger(0); public static void main(String[] args) { TestJoinMethod testJoinMethod = new TestJoinMethod(); testJoinMethod.execute(); } public void execute() { Thread t1 = new Thread(new Runnable() { @Override public void run() { for(int i=0; i<10000; i++) { count.incrementAndGet(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { for(int i=0; i<10000; i++) { count.incrementAndGet(); } } }); t1.start(); t2.start(); try { t1.join(); System.out.println(count.get()); // 此时t2可能还在跑,输出是t1完成的10000加上t2已执行的部分 t2.join(); System.out.println(count.get()); // 此时输出20000 } catch (InterruptedException e) { throw new RuntimeException(e); } } }
注意:方案2中第一个打印的结果依然可能不是10000,因为t2和t1是同时运行的,如果你需要第一个打印必须是10000,还是得用方案1的顺序启动方式。
内容的提问来源于stack exchange,提问作者nupur_s
相关产品推荐
相关产品推荐

