順序執(zhí)行性內(nèi)存模型: 是一個(gè)理論模型,人頭腦中認(rèn)為的正確的內(nèi)存模型。 順序一致性: 如果程序是正確同步的,程序的執(zhí)行將具有順序一致性;也可以說(shuō)成正確同步程序的執(zhí)行結(jié)果與順序一致性內(nèi)存模型的執(zhí)行結(jié)果相同。(同步是廣義的同步,包括同步原語(yǔ) synchronized、volatile、final 的正確使用)
優(yōu)勢(shì):編譯器和處理器為了優(yōu)化程序性能,可以對(duì)指令序列進(jìn)行重新排序。
重排序不影響單線程程序的執(zhí)行結(jié)果:因?yàn)橹嘏判虮仨氉袷財(cái)?shù)據(jù)依賴性,只有相互之間無(wú)依賴的的指令才可以重排序,而對(duì)無(wú)依賴的指令進(jìn)行重排序不影響單線程程序的執(zhí)行結(jié)果。
重排序會(huì)影響多線程程序的執(zhí)行結(jié)果,包括:
編譯期優(yōu)化的重排序:編譯器在不改變單線程程序語(yǔ)義的前提下,可以重新安排語(yǔ)句的執(zhí)行順序。指令級(jí)并行的重排序:如果不存在數(shù)據(jù)依賴性,處理器可以改變語(yǔ)句對(duì)應(yīng)的機(jī)器指令的執(zhí)行順序。下面舉例說(shuō)明:
上述代碼中,假設(shè)線程A 首先執(zhí)行 write() 而線程B 后執(zhí)行 read(),則可能的時(shí)序?yàn)椋?/p>

最后得到結(jié)果為:
a = 2 flag = true
而由于重排序,1 和 2 可能會(huì)進(jìn)行互換,得到的時(shí)序?yàn)椋?/p>

最后得到結(jié)果為:
a = 1 flag = true
可以看到重排序?qū)е铝藘煞N不同的執(zhí)行結(jié)果;可以使用同步解決,如下:
class RecorderExample{ int a = 0; boolean flag = false; public synchronized void write(){ a = 1; flag = true; } public synchronized void read(){ if(!flag){ a = 2; } }}同樣,假設(shè)線程A 首先執(zhí)行 write()和線程B 后執(zhí)行 read(),對(duì)比一下 JMM 中的執(zhí)行時(shí)序和順序一致性內(nèi)存模型中的執(zhí)行時(shí)序:

因?yàn)榧恿随i同步,所以同步塊內(nèi)的重排序并不影響多線程的執(zhí)行結(jié)果。
JMM的基本方針是在不改變正確同步程序的執(zhí)行結(jié)果的情況下,盡可能地利用重排序進(jìn)行性能優(yōu)化。因此同步塊內(nèi)臨界區(qū)的重排序是允許的。 針對(duì)未同步程序(包括單線程和多線程),順序一致性內(nèi)存模型能保證其順序一致性,JMM 不保證其順序一致性。
以雙重檢查鎖定為例:
public class Singleton { PRivate static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }}對(duì)象的初始化可以分解為以下偽代碼:
memory = allocate(); // 1:分配對(duì)象的內(nèi)存空間ctorInstance(memory); // 2:初始化對(duì)象instance = memory; // 3:設(shè)置 instance 指向內(nèi)存空間而編譯器很可能為了優(yōu)化程序的執(zhí)行效率進(jìn)行指令重排序,即對(duì) 2 和 3 進(jìn)行互換
memory = allocate(); // 1:分配對(duì)象的內(nèi)存空間instance = memory; // 3:設(shè)置 instance 指向內(nèi)存空間 // 注意,此時(shí)對(duì)象還沒(méi)有被初始化!ctorInstance(memory); // 2:初始化對(duì)象在進(jìn)行了上述重排序的情況下,可能造成下面的執(zhí)行順序:

上述過(guò)程中,線程B 訪問(wèn) instance 指向的對(duì)象的時(shí)候該對(duì)象尚未完成初始化,會(huì)引起各種幺蛾子的,請(qǐng)慎重。
解決辦法:很簡(jiǎn)單,使用 volatile 修飾 instance 變量即可;
private volatile static Singleton instance = null;volatile 關(guān)鍵字通過(guò)提供內(nèi)存避障的方式禁止指令重排序。
單線程中會(huì)不會(huì)得到一個(gè)尚未完全初始化的對(duì)象呢?(不會(huì)) 因?yàn)?java 語(yǔ)言規(guī)范中 intra-thread semantics(線程內(nèi)語(yǔ)義)保證了重排序不會(huì)影響單線程程序的執(zhí)行結(jié)果。怎么理解呢?單線程中,也允許上述重排序,但是當(dāng)你使用 instance 引用的時(shí)候其指向的對(duì)象已經(jīng)初始化成功了。
新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注