小白,坐在這間屬于華夏國超一流互聯(lián)網(wǎng)公司企鵝巴巴的小會(huì)議室里,等著技術(shù)面試官的到來。
小伙子我看你簡歷上什么也沒寫,這次也是第一面,那我們就隨便問點(diǎn)簡單的多線程問題吧。先說說什么是Java的多線程吧,使用多線程有什么好處?有什么壞處?
媽媽說專家的話不能信!果然,問個(gè)多線程還問好處壞處?我不想用不會(huì)用能進(jìn)企鵝巴巴么?
但是作為打工人,我認(rèn)真的回答道:
Java的多線程是指程序中包含多個(gè)執(zhí)行流,即在一個(gè)程序中可以同時(shí)運(yùn)行多個(gè)不同的線程來執(zhí)行不同的任務(wù)。
而使用多線程的好處是可以提高 CPU 的利用率。在多線程程序中,一個(gè)線程必須等待的時(shí)候,CPU 可以運(yùn)行其它的線程而不是等待,這樣就大大提高了程序的效率。也就是說允許單個(gè)程序創(chuàng)建多個(gè)并行執(zhí)行的線程來完成各自的任務(wù)。
至于多線程的壞處么,主要有三點(diǎn)。第一點(diǎn)是線程也是程序,所以線程需要占用內(nèi)存,線程越多占用內(nèi)存也越多;第二點(diǎn)是多線程需要協(xié)調(diào)和管理,所以需要 CPU 時(shí)間跟蹤線程;最后是線程之間對(duì)共享資源的訪問會(huì)相互影響,必須解決競用共享資源的問題。
你剛才講了“并行”這個(gè)詞,那你說說并行和并發(fā)有什么區(qū)別?
并發(fā),英文單詞是concurrency,就是多個(gè)任務(wù)在同一個(gè) CPU 核上,按細(xì)分的時(shí)間片輪流(交替)執(zhí)行,從邏輯上來看那些任務(wù)是同時(shí)執(zhí)行。
并行,英文單詞是parallelism,就是單位時(shí)間內(nèi),多個(gè)處理器或多核處理器同時(shí)處理多個(gè)任務(wù),是真正意義上的“同時(shí)進(jìn)行”。
這兩句話,我相信99%的同學(xué)都知道!但是,如果想進(jìn)企鵝巴巴,如果想應(yīng)付P20的科學(xué)家!我就一定要自行的結(jié)合業(yè)務(wù)回答并發(fā)并行的優(yōu)勢!
現(xiàn)在的系統(tǒng)動(dòng)不動(dòng)就要求百萬級(jí)甚至千萬級(jí)的并發(fā)量,而多線程并發(fā)編程正是開發(fā)高并發(fā)系統(tǒng)的基礎(chǔ),利用好多線程機(jī)制可以大大提高系統(tǒng)整體的并發(fā)能力以及性能。面對(duì)復(fù)雜業(yè)務(wù)模型,并行程序會(huì)比串行程序更適應(yīng)業(yè)務(wù)需求,而并發(fā)編程更能吻合這種業(yè)務(wù)拆分 。
路人S和路人B果然都露出了滿意的笑容。
那你說說看,在操作系統(tǒng)中用戶級(jí)線程和內(nèi)核級(jí)線程是什么?這兩個(gè)線程在多核CPU的計(jì)算機(jī)上是否都能并行?
在操作系統(tǒng)的設(shè)計(jì)中,為了防止用戶操作敏感指令而對(duì)OS帶來安全隱患,我們把OS分成了用戶空間(user space)和內(nèi)核空間(kernel space)。
通過用戶空間的庫類實(shí)現(xiàn)的線程,就是用戶級(jí)線程(user-level threads,ULT)。這種線程不依賴于操作系統(tǒng)核心,進(jìn)程利用線程庫提供創(chuàng)建、同步、調(diào)度和管理線程的函數(shù)來控制用戶線程。
說著,我拿了一支筆,畫了這么一張圖:
在圖里,我們可以清楚的看到,線程表(管理線程的數(shù)據(jù)結(jié)構(gòu))是處于進(jìn)程內(nèi)部的,完全處于用戶空間層面,內(nèi)核空間對(duì)此一無所知!當(dāng)然,用戶線程也可以沒有線程表!
相應(yīng)的,由OS內(nèi)核空間直接掌控的線程,稱為內(nèi)核級(jí)線程(kernel-level threads,KLT)。其依賴于操作系統(tǒng)核心,由內(nèi)核的內(nèi)部需求進(jìn)行創(chuàng)建和撤銷。
接著,我畫下了這張圖:
同樣的,在圖中,我們看到內(nèi)核線程的線程表(thread table)位于內(nèi)核中,包括了線程控制塊(TCB),一旦線程阻塞,內(nèi)核會(huì)從當(dāng)前或者其他進(jìn)程(process)中重新選擇一個(gè)線程保證程序的執(zhí)行。
對(duì)于用戶級(jí)線程來說,其線程的切換發(fā)生在用戶空間,這樣的線程切換至少比陷入內(nèi)核要快一個(gè)數(shù)量級(jí)。但是該種線程有個(gè)嚴(yán)重的缺點(diǎn):如果一個(gè)線程開始運(yùn)行,那么該進(jìn)程中其他線程就不能運(yùn)行,除非第一個(gè)線程自動(dòng)放棄CPU。因?yàn)樵谝粋€(gè)單獨(dú)的進(jìn)程內(nèi)部,沒有時(shí)鐘中斷,所以不能用輪轉(zhuǎn)調(diào)度(輪流)的方式調(diào)度線程。
也就是說,同一進(jìn)程中的用戶級(jí)線程,在不考慮調(diào)起多個(gè)內(nèi)核級(jí)線程的基礎(chǔ)上,是沒有辦法利用多核CPU的,其實(shí)質(zhì)是并發(fā)而非并行。
對(duì)于內(nèi)核級(jí)線程來說,其線程在內(nèi)核中創(chuàng)建和撤銷線程的開銷比較大,需要考慮上下文切換的開銷。
但是,內(nèi)核級(jí)線程是可以利用多核CPU的,即可以并行!
這回答的累死我了,不過為了能進(jìn)企鵝巴巴,走向人生巔峰,一切都值了!
嗯,小伙子基礎(chǔ)還是比較牢靠的!那你說說Java里的多線程是用戶級(jí)線程還是內(nèi)核級(jí)線程呢?
是...當(dāng)我要脫口而出的時(shí)候,發(fā)現(xiàn)不對(duì),這面試官在套路我!堂堂科學(xué)家,套路還沒入職的孩子么?
Java里的多線程,既不是用戶級(jí)線程,也不是內(nèi)核級(jí)線程!
首先,Java是跨操作平臺(tái)的語言,是使用JVM去運(yùn)行編譯文件的。不同的JVM對(duì)線程的實(shí)現(xiàn)不同,相同的JVM對(duì)不同操作平臺(tái)的線程實(shí)現(xiàn)方式也有區(qū)別!
其次,要講明白程序級(jí)別實(shí)現(xiàn)多線程,就必須先說一下多線程模型。
裂開!怎么感覺這又是一道大題?。是操作系統(tǒng)的科學(xué)家吧!感覺問的都是很底層的東西了啊,現(xiàn)在程序員內(nèi)卷成這樣了么?實(shí)習(xí)生都問這么底層的問題了?雖然百般不爽,但是為了拿下美女HR,不!是橫掃offer。我要給路人B講明白這個(gè)線程模型!
上面我說過OS上的線程分為ULT和KLT,我們寫程序的代碼只能是在用戶空間里寫代碼!而程序運(yùn)行中,基本上都會(huì)進(jìn)入內(nèi)核運(yùn)行,所以我們?cè)趯?shí)現(xiàn)程序級(jí)別多線程的時(shí)候,必須讓ULT映射到KLT上去。在程序級(jí)別的多線程設(shè)計(jì)里,有以下三種多線程模型。
多對(duì)1模型:在多對(duì)一模型中,多個(gè)ULT映射到1個(gè)KLT上去,此時(shí)ULT的進(jìn)程表處于進(jìn)程之中。
1對(duì)1模型:在一對(duì)一模型中,1個(gè)ULT對(duì)應(yīng)1個(gè)KLT。自己不在進(jìn)程中創(chuàng)建線程表來管理,幾行代碼之后直接通過系統(tǒng)調(diào)用調(diào)起KLT就能實(shí)現(xiàn)。
多對(duì)多模型:在多對(duì)多模型中,N個(gè)ULT對(duì)應(yīng)小于等于N個(gè)的KLT。這種模型結(jié)合了1對(duì)1和多對(duì)1的優(yōu)點(diǎn),用戶創(chuàng)建線程沒有限制,阻塞內(nèi)核系統(tǒng)的命令不會(huì)阻塞整個(gè)進(jìn)程。
最后,就拿最熱門的HotSpot VM來說吧,他在Solaris上就有兩種線程實(shí)現(xiàn)方式,可以讓用戶選擇一對(duì)一或多對(duì)多這兩種模型;而在Windows和Linux下,使用的都是一對(duì)一的多線程模型,Java的線程通過一一映射到Light Weight Process(輕量級(jí)進(jìn)程,LWP)從而實(shí)現(xiàn)了和KLT的一一對(duì)應(yīng)。
ULT如何映射到KLT?怎么調(diào)起的?
ULT在執(zhí)行的過程中,如果執(zhí)行的指令需要進(jìn)入內(nèi)核態(tài),則ULT會(huì)通過系統(tǒng)調(diào)用調(diào)起一個(gè)KLT!
所謂系統(tǒng)調(diào)度,就是在OS中分割用戶空間和內(nèi)核空間的API。
ULT的執(zhí)行過程中可以不調(diào)起KLT么?舉個(gè)例子。
可以不調(diào)起,比如ULT中就只有sleep這個(gè)指令,就不會(huì)進(jìn)入內(nèi)核態(tài)執(zhí)行,更不會(huì)調(diào)起KLT。
問到這里,我有點(diǎn)吐血了都!看著B對(duì)我的回答很滿意,我心中卻把B已經(jīng)問候了一百遍!
看來同學(xué)對(duì)于底層的知識(shí)理解還湊合,那你有沒有看過HotSpot的源碼?能不能簡單說說看Java的線程是怎么運(yùn)行的?
這問的還上癮了?P20的問題咋這么“簡單”呢!說實(shí)話,自從前幾天發(fā)生了靈異事件之后,我確實(shí)技術(shù)突飛猛進(jìn),這個(gè)源代碼我好像還真的瞄了一眼,不過我不能暴露自己擁有金手指的秘密??!
于是我撓了撓頭,思考了1分鐘,然后說道:
源碼以前看過,只能記得一個(gè)大概。
1、在Java中,使用java.lang.Thread的構(gòu)造方法來構(gòu)建一個(gè)java.lang.Thread對(duì)象,此時(shí)只是對(duì)這個(gè)對(duì)象的部分字段(例如線程名,優(yōu)先級(jí)等)進(jìn)行初始化;
2、調(diào)用java.lang.Thread對(duì)象的start()方法,開始此線程。此時(shí),在start()方法內(nèi)部,調(diào)用start0() 本地方法來開始此線程;
3、start0()在VM中對(duì)應(yīng)的是JVM_StartThread,也就是,在VM中,實(shí)際運(yùn)行的是JVM_StartThread方法(宏),在這個(gè)方法中,創(chuàng)建了一個(gè)JavaThread對(duì)象;
4、在JavaThread對(duì)象的創(chuàng)建過程中,會(huì)根據(jù)運(yùn)行平臺(tái)創(chuàng)建一個(gè)對(duì)應(yīng)的OSThread對(duì)象,且JavaThread保持這個(gè)OSThread對(duì)象的引用;
5、在OSThread對(duì)象的創(chuàng)建過程中,創(chuàng)建一個(gè)平臺(tái)相關(guān)的底層級(jí)線程,如果這個(gè)底層級(jí)線程失敗,那么就拋出異常;
6、在正常情況下,這個(gè)底層級(jí)的線程開始運(yùn)行,并執(zhí)行java.lang.Thread對(duì)象的run方法;
7、當(dāng)java.lang.Thread生成的Object的run()方法執(zhí)行完畢返回后,或者拋出異常終止后,終止native thread;
8、最后就是釋放相關(guān)的資源(包括內(nèi)存、鎖等)
大概就是以上這么個(gè)步驟吧。
回答完這個(gè),我要跪謝我的金手指了!我看見路人S在電腦上敲著什么,估計(jì)他也比較懵,沒想到我居然能答得上來吧!
那你說說什么是上下文切換吧。
多線程編程中一般線程的個(gè)數(shù)都大于 CPU 核心的個(gè)數(shù),而一個(gè) CPU 核心在任意時(shí)刻只能被一個(gè)線程使用,為了讓這些線程都能得到有效執(zhí)行,CPU 采取的策略是為每個(gè)線程分配時(shí)間片并輪轉(zhuǎn)的形式。
時(shí)間片是CPU分配給各個(gè)線程的時(shí)間,因?yàn)闀r(shí)間非常短,所以CPU不斷通過切換線程,讓我們覺得多個(gè)線程是同時(shí)執(zhí)行的,時(shí)間片一般是幾十毫秒。
當(dāng)一個(gè)線程的時(shí)間片用完的時(shí)候就會(huì)重新處于就緒狀態(tài)讓給其他線程使用,這個(gè)過程就屬于一次上下文切換。
概括來說就是:當(dāng)前任務(wù)在執(zhí)行完 CPU 時(shí)間片切換到另一個(gè)任務(wù)之前會(huì)先保存自己的狀態(tài),以便下次再切換回這個(gè)任務(wù)時(shí),可以再加載這個(gè)任務(wù)的狀態(tài)。任務(wù)從保存到再加載的過程就是一次上下文切換。
頻繁切換上下文會(huì)有什么問題?
上下文切換通常是計(jì)算密集型的,每次切換時(shí),需要保存當(dāng)前的狀態(tài)起來,以便能夠進(jìn)行恢復(fù)先前狀態(tài),而這個(gè)切換時(shí)非常損耗性能。
也就是說,它需要相當(dāng)可觀的處理器時(shí)間,在每秒幾十上百次的切換中,每次切換都需要納秒量級(jí)的時(shí)間。所以,上下文切換對(duì)系統(tǒng)來說意味著消耗大量的 CPU 時(shí)間,事實(shí)上,可能是操作系統(tǒng)中時(shí)間消耗最大的操作。
Linux 相比與其他操作系統(tǒng)(包括其他類 Unix 系統(tǒng))有很多的優(yōu)點(diǎn),其中有一項(xiàng)就是,其上下文切換和模式切換的時(shí)間消耗非常少。
減少上下文切換的方式有哪些?
通常減少上下文切換的方式有:
1、無鎖并發(fā)編程:可以參照concurrentHashMap鎖分段的思想,不同的線程處理不同段的數(shù)據(jù),這樣在多線程競爭的條件下,可以減少上下文切換的時(shí)間。
2、CAS算法:利用Atomic下使用CAS算法來更新數(shù)據(jù),使用了樂觀鎖,可以有效的減少一部分不必要的鎖競爭帶來的上下文切換。
3、使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多的線程,這樣會(huì)造成大量的線程都處于等待狀態(tài)。
4、協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。
協(xié)程是什么?和用戶線程有什么區(qū)別?
我聽了真想抽自己幾個(gè)嘴巴子,怎么又來了!B是只會(huì)OS吧!
協(xié)程的英文單詞是Coroutine,這是一個(gè)程序組件,它既不是線程也不是進(jìn)程。它的執(zhí)行過程更類似于一個(gè)方法,或者說不帶返回值的函數(shù)調(diào)用。
我看到過stack overflow和很多博客里,都認(rèn)為這兩者是一個(gè)東西。但是,在我的理解中,這兩者還是有區(qū)別的。
不可否認(rèn)的是,協(xié)程和ULT做的是同一個(gè)事情。所以從某種角度上講,他們確實(shí)是等價(jià)的!
但是,ULT這個(gè)概念被提出的時(shí)候,其背后的思想本質(zhì)是講ULT是個(gè)本機(jī)線程,也就是使用了OS的用戶空間內(nèi)提供的庫類直接創(chuàng)建的線程。這個(gè)時(shí)候,你不需要在OS上面添加一些其他第三方的庫類。
而協(xié)程這個(gè)概念是康威定律的提出者M(jìn)elvin Edward Conway在1958年提出的一個(gè)概念,其背后的思想是不直接使用OS本身的庫類,自己做一些庫類去實(shí)現(xiàn)并發(fā)。在那個(gè)年代,OS上面的第三方庫類并不像現(xiàn)在這么流行,OS本身的庫類和其他第三方庫類的結(jié)合也并不像今天這么容易。所以協(xié)程并不是本機(jī)線程,他是需要借助一些其他不屬于OS的第三方庫類調(diào)用OS用戶空間的庫類來實(shí)現(xiàn)達(dá)到ULT的效果。
當(dāng)然,這個(gè)概念在今天來看,就會(huì)顯得很讓人混淆了。因?yàn)榈降啄男祛愃闶荗S本機(jī)的庫類,哪些算是第三方庫類?這和1960年的時(shí)候已經(jīng)有絕大的區(qū)別了!所以大家認(rèn)為這兩者是一個(gè)東西,其實(shí)也不能說他說的不對(duì),只能說可能對(duì)這個(gè)思想本身背后代表的東西不明白。
那你知道fiber么?這個(gè)和上面兩個(gè)名詞有什么區(qū)別?
fiber也是一種本機(jī)線程,其本質(zhì)是一種特殊的ULT,即更輕量級(jí)的ULT。說白了就是這種ULT的線程表一定存于進(jìn)程之中。
而我們?cè)跇?gòu)建一對(duì)一多線程模型的時(shí)候,ULT的線程表其實(shí)還是交給內(nèi)核了!這是兩者之間最直接的差別。所以我們經(jīng)常稱fiber就是協(xié)同調(diào)度的ULT,在win32中可以調(diào)用fiber來構(gòu)建多對(duì)多的多線程模型。
其實(shí),fiber、coroutine和ULT在用戶層面能看到的效果是基本等價(jià)的。
其中ULT是描述OS庫本身提供的功能;fiber描述的是OS提供的協(xié)同調(diào)度的ULT;coroutine描述的是第三方實(shí)現(xiàn)的并發(fā)并行功能。
這些名詞很多都是歷史原因的問題,同時(shí)也是深入研究需要了解的事情,我們普通程序員在使用的時(shí)候,更多的關(guān)心的是應(yīng)用層方面的東西。而這些名詞的理解已經(jīng)深入到源碼層了。
還是講講看在 Java 程序中怎么保證多線程的運(yùn)行安全吧。
Java的線程安全在三個(gè)方面體現(xiàn):
原子性:提供互斥訪問,同一時(shí)刻只能有一個(gè)線程對(duì)數(shù)據(jù)進(jìn)行操作,在Java中使用了atomic和synchronized這兩個(gè)關(guān)鍵字來確保原子性;
可見性:一個(gè)線程對(duì)主內(nèi)存的修改可以及時(shí)地被其他線程看到,在Java中使用了synchronized和volatile這兩個(gè)關(guān)鍵字確保可見性;
有序性:一個(gè)線程觀察其他線程中的指令執(zhí)行順序,由于指令重排序,該觀察結(jié)果一般雜亂無序,在Java中使用了happens-before原則來確保有序性。
你剛才講了有序性,那你說說代碼為什么會(huì)重排序?
在執(zhí)行程序時(shí),為了提高性能,處理器和編譯器常常會(huì)對(duì)指令進(jìn)行重排序。
重排序是想怎么重排就重排么?
這面試官也很難纏啊,怎么一直在追問,是需要我給他孝敬一根華子么?要不是看著旁邊有個(gè)美女HR,我早就孝敬S他老人家了!
當(dāng)然不是!不能隨意重排序,不是你想怎么排序就怎么排序,它需要滿足以下兩個(gè)條件:
1、在單線程環(huán)境下不能改變程序運(yùn)行的結(jié)果;
2、存在數(shù)據(jù)依賴關(guān)系的不允許重排序。
所以重排序不會(huì)對(duì)單線程有影響,只會(huì)破壞多線程的執(zhí)行語義。
那你講講看在Java中如何保障重排序不影響單線程的吧。
保障這一結(jié)果是因?yàn)樵诰幾g器,runtime 和處理器都必須遵守as-if-serial語義規(guī)則。
為了遵守as-if-serial語義,編譯器和處理器不會(huì)對(duì)存在數(shù)據(jù)依賴關(guān)系的操作做重排序,因?yàn)檫@種重排序會(huì)改變執(zhí)行結(jié)果。但是,如果操作之間不存在數(shù)據(jù)依賴關(guān)系,這些操作可能被編譯器和處理器重排序。
我來舉個(gè)例子吧
說著我拿著筆在紙上寫了三行簡單的代碼:
我們看這個(gè)例子,A和C之間存在數(shù)據(jù)依賴關(guān)系,同時(shí)B和C之間也存在數(shù)據(jù)依賴關(guān)系。因此在最終執(zhí)行的指令序列中,C不能被重排序到A和B的前面,如果C排到A和B的前面,那么程序的結(jié)果將會(huì)被改變。但A和B之間沒有數(shù)據(jù)依賴關(guān)系,編譯器和處理器可以重排序A和B之間的執(zhí)行順序。
這就是as-if-serial語義。
那你說說看你剛才講的happens-before原則吧。
happens-before說白了就是誰在誰前面發(fā)生的一個(gè)關(guān)系。
HB規(guī)則是Java內(nèi)存模型(JMM)向程序員提供的跨線程內(nèi)存可見性保證。
說的直白一點(diǎn),就是如果A線程的寫操作a與B線程的讀操作b之間存在happens-before關(guān)系,盡管a操作和b操作在不同的線程中執(zhí)行,但JMM向程序員保證a操作將對(duì)b操作可見。
具體的定義為:
1、如果一個(gè)操作happens-before另一個(gè)操作,那么第一個(gè)操作的執(zhí)行結(jié)果將對(duì)第二個(gè)操作可見,而且第一個(gè)操作的執(zhí)行順序排在第二個(gè)操作之前。
2、兩個(gè)操作之間存在happens-before關(guān)系,并不意味著Java平臺(tái)的具體實(shí)現(xiàn)必須要按照happens-before關(guān)系指定的順序來執(zhí)行。如果重排序之后的執(zhí)行結(jié)果,與按happens-before關(guān)系來執(zhí)行的結(jié)果一致,那么這種重排序并不非法。
具體的規(guī)則有8條:
1、程序順序規(guī)則:一個(gè)線程中的每個(gè)操作,happens-before于該線程中的任意后續(xù)操作。
2、監(jiān)視器鎖規(guī)則:對(duì)一個(gè)鎖的解鎖,happens-before于隨后對(duì)這個(gè)鎖的加鎖。
3、volatile變量規(guī)則:對(duì)一個(gè)volatile域的寫,happens-before于任意后續(xù)對(duì)這個(gè)volatile域的讀。
4、傳遞性:如果A happens-before B,且B happens-before C,那么A happens-before C。
5、start()規(guī)則:如果線程A執(zhí)行操作ThreadB.start()(啟動(dòng)線程B),那么A線程的ThreadB.start()操作happens-before于線程B中的任意操作。
6、Join()規(guī)則:如果線程A執(zhí)行操作ThreadB.join()并成功返回,那么線程B中的任意操作happens-before于線程A從ThreadB.join()操作成功返回。
7、程序中斷規(guī)則:對(duì)線程interrupted()方法的調(diào)用先行于被中斷線程的代碼檢測到中斷時(shí)間的發(fā)生。
8、對(duì)象finalize規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行于發(fā)生它的finalize()方法的開始。
你剛才說HB規(guī)則不代表最終的執(zhí)行順序,能不能舉個(gè)例子。
就拿講as-if-serial提到的例子舉例吧,例子很簡單就是面積=寬*高。
利用HB的程序順序規(guī)則,存在三個(gè)happens-before關(guān)系:
1、A happens-before B;
2、B happens-before C;
3、A happens-before C。
這里的第三個(gè)關(guān)系是利用傳遞性進(jìn)行推論的。這里的第三個(gè)關(guān)系是利用傳遞性進(jìn)行推論的。
A happens-before B,定義1要求A執(zhí)行結(jié)果對(duì)B可見,并且A操作的執(zhí)行順序在B操作之前;但與此同時(shí)利用HB定義中的第二條,A、B操作彼此不存在數(shù)據(jù)依賴性,兩個(gè)操作的執(zhí)行順序?qū)ψ罱K結(jié)果都不會(huì)產(chǎn)生影響。
在不改變最終結(jié)果的前提下,允許A,B兩個(gè)操作重排序,即happens-before關(guān)系并不代表了最終的執(zhí)行順序。
哈嘍,我是小林,就愛圖解計(jì)算機(jī)基礎(chǔ),如果覺得文章對(duì)你有幫助,歡迎分享給你的朋友,也給小林點(diǎn)個(gè)「在看」,這對(duì)小林非常重要,謝謝你們,給各位小姐姐小哥哥們抱拳了,我們下次見!
推薦閱讀
你不好奇 Linux 是如何收發(fā)網(wǎng)絡(luò)包的?
小小的 float,藏著大大的學(xué)問
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場,如有問題,請(qǐng)聯(lián)系我們,謝謝!