问题描述
提示:这里描述项目中遇到的问题:
在学习并发编程过程中,发现多线程的执行顺序可能会导致运行结果的不同。
// a为初始值为1的共享变量,这里如果thread2先执行,则程序可以正常退出,thread先执行,程序阻塞
@Test
public void test2() throws InterruptedException {
Thread thread2 = new Thread(() -> {
System.out.println("start change");
flag = true;
a = 2;
System.out.println(flag);
System.out.println("end change");
});
thread2.start();
Thread.sleep(1000);
Thread thread = new Thread(() -> {
System.out.println("start....");
int cnt = 0;
while (a==1){
}
System.out.println("a="+a);
System.out.println("cnt:"+cnt);
System.out.println("end....");
});
thread.start();
}
原因分析:
这里基于JMM内存模型进行分析
Thread2先执行的情况:
- Thread2执行到a=2这条指令,将主内存中的变量a经过总线加载到线程内部的工作内存,即此时Thread2的工作内存中,有这个变量a的副本,此时a=1。
- Thread2执行a=2,底层通过赋值命令将工作内存中的变量a的值修改为2,同时再通过总线发送指令,将主内存中的a修改为2
- Thread2执行完毕后,Thread1执行循环,这里就直接读取了主内存中的a(因为此时Thread1线程的工作内存中,没有a的副本,需要从主内存中load),之后执行跳出循环,程序结束
Thread1先执行的情况:
- Thread1先从主内存中load对应的a变量,在工作内存中创建副本,放在线程内部寄存器的缓存行中,此时缓存的a=1,进入循环。
- Thread2读取、修改、返回a=2,此时主内存中的a为2。
- 由于Thread1中寄存器中a对应的缓存行仍然是有效状态,Thread1无法得知主内存中的a已经改变,因此进入死循环。
小结:
- 线程的运行顺序会对程序造成影响。
- 在进行多线程并发编程时,尽量避免使用System.out.println进行打印,println底层使用synchronized关键字加锁打印,使得类被加锁,保证了可见性、有序性以及原子性。
- 在Thread1先运行的情况下,如果要使程序正常运行,可以对变量a加上volatile进行修饰,底层通过缓存一致性协议、禁止指令重排序、总线嗅探判断缓存行是否失效。