线程同步疑问:为何不同代码执行下interview.b输出值不同?
问题:synchronized方法执行导致的变量值差异分析
测试代码
public class Interview implements Runnable { int b = 100; public synchronized void m1() throws Exception { //System.out.println("-----"); b = 1000; Thread.sleep(5000); System.out.println("b = " + b); } public synchronized void m2() throws Exception { Thread.sleep(2500); b = 2000; } @Override public void run() { try { m1(); } catch (Exception e) { throw new RuntimeException(e); } } public static void main(String[] args) throws Exception { Interview interview = new Interview(); Thread thread = new Thread(interview); thread.start(); interview.m2(); System.out.println(interview.b); } }
两种执行情况的输出
情况1:注释掉m1中的System.out.println("-----");
1000 b = 1000
情况2:不注释该语句
2000 ----- b = 1000
差异原因分析
这本质是线程启动的不确定性加上synchronized锁的竞争,再配合System.out.println的同步特性共同导致的:
- 首先,
thread.start()只是通知JVM启动新线程,不是立刻执行,主线程会继续往下调用interview.m2()。 m1和m2都是用synchronized修饰的实例方法,锁定的是同一个interview对象,所以同一时间只有一个线程能执行其中任意一个方法。
注释掉println的场景:
新线程启动后,因为m1的代码更简洁(没有额外的IO和同步操作),JVM大概率会先调度它抢到锁:
- 新线程进入
m1,先把b改成1000,然后睡5秒; - 主线程调用
m2时发现锁被占,只能阻塞等待; - 新线程睡醒后打印
b=1000,释放锁; - 主线程拿到锁进入
m2,睡2.5秒后把b改成2000,但主线程的System.out.println(interview.b);是在调用m2之后立刻执行的——当时m2还在阻塞状态,所以输出的是新线程已经修改过的1000。
不注释println的场景:
System.out.println内部自带synchronized同步块,还涉及IO操作,会给新线程的执行带来额外延迟:
- 主线程在新线程还没抢到锁的时候,先进入
m2并拿到锁; - 主线程睡2.5秒后把
b改成2000,执行完m2释放锁; - 主线程立刻执行
System.out.println(interview.b);,输出2000; - 这时候新线程终于抢到锁进入
m1,先打印-----,把b改成1000,睡5秒后打印b=1000。
总结来说,两种情况的差异就是**System.out.println的存在改变了线程调度的时机,导致哪个线程先抢到锁的结果不同**,最终影响了主线程输出b时的取值。
内容的提问来源于stack exchange,提问作者苏苏沐哲
相关产品推荐
相关产品推荐

