多线程数组可见性问题:不同长度数组为何执行结果不同?
线程可见性与JIT优化导致的差异行为解释
在Windows环境下运行下述代码时会出现两种不同结果:
- 当线程t2检查数组全部200个元素时,程序最终会正常终止
- 当t2只检查更少元素(比如前100个)时,程序会陷入无限循环
这段代码原本是用来演示线程间的内存可见性问题:线程t1对数组的赋值操作,线程t2无法感知,而通过volatile修饰数组可以解决该问题。
示例代码
public static void main(String[] args) throws InterruptedException { int[] arr = new int[200]; for (int i=0; i<arr.length; i++) { arr[i] = 0; } Thread t1 = new Thread(() -> { try { Thread.sleep(100); } catch (InterruptedException e) { throw new RuntimeException(e); } for (int i=0; i<arr.length; i++) { arr[i] = 1; } }); Thread t2 = new Thread(() -> { while (true) { if (arr[0] == 1 || arr[1] == 1 || arr[2] == 1 || arr[3] == 1 || arr[4] == 1 || arr[5] == 1 || arr[6] == 1 || arr[7] == 1 || arr[8] == 1 || arr[9] == 1 || arr[10] == 1 || arr[11] == 1 || arr[12] == 1 || arr[13] == 1 || arr[14] == 1 || arr[15] == 1 || arr[16] == 1 || arr[17] == 1 || arr[18] == 1 || arr[19] == 1 || arr[20] == 1 || arr[21] == 1 || arr[22] == 1 || arr[23] == 1 || arr[24] == 1 || arr[25] == 1 || arr[26] == 1 || arr[27] == 1 || arr[28] == 1 || arr[29] == 1 || arr[30] == 1 || arr[31] == 1 || arr[32] == 1 || arr[33] == 1 || arr[34] == 1 || arr[35] == 1 || arr[36] == 1 || arr[37] == 1 || arr[38] == 1 || arr[39] == 1 || arr[40] == 1 || arr[41] == 1 || arr[42] == 1 || arr[43] == 1 || arr[44] == 1 || arr[45] == 1 || arr[46] == 1 || arr[47] == 1 || arr[48] == 1 || arr[49] == 1 || arr[50] == 1 || arr[51] == 1 || arr[52] == 1 || arr[53] == 1 || arr[54] == 1 || arr[55] == 1 || arr[56] == 1 || arr[57] == 1 || arr[58] == 1 || arr[59] == 1 || arr[60] == 1 || arr[61] == 1 || arr[62] == 1 || arr[63] == 1 || arr[64] == 1 || arr[65] == 1 || arr[66] == 1 || arr[67] == 1 || arr[68] == 1 || arr[69] == 1 || arr[70] == 1 || arr[71] == 1 || arr[72] == 1 || arr[73] == 1 || arr[74] == 1 || arr[75] == 1 || arr[76] == 1 || arr[77] == 1 || arr[78] == 1 || arr[79] == 1 || arr[80] == 1 || arr[81] == 1 || arr[82] == 1 || arr[83] == 1 || arr[84] == 1 || arr[85] == 1 || arr[86] == 1 || arr[87] == 1 || arr[88] == 1 || arr[89] == 1 || arr[90] == 1 || arr[91] == 1 || arr[92] == 1 || arr[93] == 1 || arr[94] == 1 || arr[95] == 1 || arr[96] == 1 || arr[97] == 1 || arr[98] == 1 || arr[99] == 1 || arr[100] == 1 || arr[101] == 1 || arr[102] == 1 || arr[103] == 1 || arr[104] == 1 || arr[105] == 1 || arr[106] == 1 || arr[107] == 1 || arr[108] == 1 || arr[109] == 1 || arr[110] == 1 || arr[111] == 1 || arr[112] == 1 || arr[113] == 1 || arr[114] == 1 || arr[115] == 1 || arr[116] == 1 || arr[117] == 1 || arr[118] == 1 || arr[119] == 1 || arr[120] == 1 || arr[121] == 1 || arr[122] == 1 || arr[123] == 1 || arr[124] == 1 || arr[125] == 1 || arr[126] == 1 || arr[127] == 1 || arr[128] == 1 || arr[129] == 1 || arr[130] == 1 || arr[131] == 1 || arr[132] == 1 || arr[133] == 1 || arr[134] == 1 || arr[135] == 1 || arr[136] == 1 || arr[137] == 1 || arr[138] == 1 || arr[139] == 1 || arr[140] == 1 || arr[141] == 1 || arr[142] == 1 || arr[143] == 1 || arr[144] == 1 || arr[145] == 1 || arr[146] == 1 || arr[147] == 1 || arr[148] == 1 || arr[149] == 1 || arr[150] == 1 || arr[151] == 1 || arr[152] == 1 || arr[153] == 1 || arr[154] == 1 || arr[155] == 1 || arr[156] == 1 || arr[157] == 1 || arr[158] == 1 || arr[159] == 1 || arr[160] == 1 || arr[161] == 1 || arr[162] == 1 || arr[163] == 1 || arr[164] == 1 || arr[165] == 1 || arr[166] == 1 || arr[167] == 1 || arr[168] == 1 || arr[169] == 1 || arr[170] == 1 || arr[171] == 1 || arr[172] == 1 || arr[173] == 1 || arr[174] == 1 || arr[175] == 1 || arr[176] == 1 || arr[177] == 1 || arr[178] == 1 || arr[179] == 1 || arr[180] == 1 || arr[181] == 1 || arr[182] == 1 || arr[183] == 1 || arr[184] == 1 || arr[185] == 1 || arr[186] == 1 || arr[187] == 1 || arr[188] == 1 || arr[189] == 1 || arr[190] == 1 || arr[191] == 1 || arr[192] == 1 || arr[193] == 1 || arr[194] == 1 || arr[195] == 1 || arr[196] == 1 || arr[197] == 1 || arr[198] == 1 || arr[199] == 1 ) { break; } } }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("finished"); }
现象原因解释
内存可见性基础问题
Java内存模型中,每个线程拥有独立的工作内存,主存为所有线程共享。线程t1修改数组元素后,修改值不会立刻同步到主存;线程t2默认只会从自身工作内存读取数组值,不会主动去主存刷新,因此可能看不到t1的修改。JIT即时编译器的循环优化差异
这是两种结果出现的核心原因:- 检查少量元素时:t2的循环体代码简短,JIT编译器会触发循环不变量优化——将数组的所有读取操作一次性缓存到CPU寄存器中,后续循环不再去主存重新读取数组值。哪怕t1已经把数组全部改为1,t2也只会读取寄存器里的旧值(全0),永远不会触发break,陷入无限循环。
- 检查全部200个元素时:循环体代码量过大,超出了JIT编译器触发激进优化的阈值,JIT不会对这段代码做缓存优化。t2每次循环都会去主存读取数组元素,最终能看到t1修改后的1值,从而跳出循环,程序正常终止。
volatile的解决原理
给数组加上volatile修饰后,会强制:- 线程每次读取数组时必须从主存获取最新值
- 线程修改数组后必须立刻把值同步到主存
- 禁止JIT编译器对该数组的读取操作做缓存优化
无论检查多少元素,t2都能及时感知到t1的修改,不会出现无限循环。
内容的提问来源于stack exchange,提问作者Noam
相关产品推荐
相关产品推荐

