講實(shí)話聽到這個(gè)問題骏啰,不太熟悉并發(fā)編程的同學(xué)有點(diǎn)暈节吮,你可能只能答個(gè)因?yàn)槎嗑€程之間的競爭共享資源啊。對說的沒錯(cuò)判耕。
但是呢感覺不夠亮眼透绩!我們的目的就是讓面試官眼前一亮,讓他顫抖壁熄!心里鼓掌:"牛批牛批!"
多線程帚豪,給我們的感覺像啥?就像《風(fēng)云》里面的無名的"萬劍歸宗"草丧!《雪中悍刀行》里面老劍神李淳罡一聲“劍來狸臣!”。嘩嘩嘩的好多劍同時(shí)操作著一起飛昌执!多帥哦烛亦!
但實(shí)際上CPU的執(zhí)行是有時(shí)間片的诈泼,也就是其實(shí)我們多線程不是同一時(shí)間一起執(zhí)行的,是每個(gè)線程執(zhí)行一會兒就換一個(gè)上煤禽,但是時(shí)間片很短铐达,所以我們感覺上是一起執(zhí)行的。當(dāng)然多核CPU的話檬果,每個(gè)CPU是并行的瓮孙,但是我們CPU顆數(shù)哪有我們的線程多!
大家都知道我們電腦有CPU选脊,內(nèi)存杭抠,IO設(shè)備。IO讀取時(shí)最慢的知牌,然后是內(nèi)存讀取祈争,最快的是CPU的執(zhí)行。那因?yàn)閮?nèi)存太慢了角寸,跟不上CPU菩混,所以CPU搞了個(gè)緩存。就是因?yàn)檫@個(gè)緩存再加上多顆CPU并發(fā)就出問題了扁藕。
舉個(gè)例子
上面這個(gè)程序很簡單沮峡,但是如果有兩個(gè)線程A和B,分別CPU-A和CPU-B中同時(shí)addOne()可能會出現(xiàn)什么問題呢亿柑?
同時(shí)的線程A把a(bǔ)從內(nèi)存中取到CPU-A緩存中邢疙,緩存中a的值是0,線程B把a(bǔ)從內(nèi)存中取到CPU-B緩存中緩存中a的值是0望薄。然后它們各自+了個(gè)1疟游。這時(shí)候在它們的各自的緩存中a的值都是1。那之后寫入內(nèi)存中痕支,是不是a就變成1了颁虐?
那我們加了兩次是不是少了一次了!這是問題就是可見性問題了卧须!CPU-A的緩存的值CPU-B不可見另绩!這就是可見性的根本原因。
我們再來說一下原子性花嘶,首先原子性指的就是一個(gè)或多個(gè)操作在CPU中執(zhí)行不會被中斷的特性稱為原子性笋籽。
我們的JAVA語言是高級語言。高級語言是怎么個(gè)情況椭员。就是我們一條java代碼涵蓋了好幾條底層指令车海。比如我們上面的a=a+1;把它轉(zhuǎn)換成CPU指令至少有三條。
第一條:把a(bǔ)從內(nèi)存拿到寄存器中隘击;
第二條:寄存器中+1侍芝;
第三條:結(jié)果寫入緩存或內(nèi)存中喘沿;
那按照CPU的原子性,它是指令級別的竭贩!一條指令是不會被中斷的,所以說在我們以為a=a+1;是原子級別的莺禁,實(shí)際上在CPU看來不是留量。
所以呢如果此時(shí)有兩個(gè)線程同時(shí)執(zhí)行,那可能線程A執(zhí)行到第二條的時(shí)候哟冬,時(shí)間片到了÷ハǎ現(xiàn)在換線程B上了,然后線程B又把a(bǔ)從內(nèi)存中拿到浩峡,然后+1返回結(jié)果可岂,此時(shí)換到了線程A,線程A繼續(xù)執(zhí)行第三條翰灾。那情況就是又錯(cuò)了缕粹。
還沒完呢!我們編譯器或解釋器的優(yōu)化可能帶來意料不到的問題纸淮!為了優(yōu)化性能平斩,它可能會改變語句執(zhí)行的順序,也就是指令重排咽块!
最經(jīng)典的就是單例的雙重檢查了绘面!
看起來都加鎖了好像沒問題了,而問題就出在 instance = new Singleton(); 上
我們認(rèn)為的new操作就是:
1.分配一塊內(nèi)存
2.在內(nèi)存上初始化Singleton
3.將內(nèi)存地址賦值給instance
而指令重排之后:
1.分配一塊內(nèi)存
2.將內(nèi)存地址分配給instance
3.初始化Singleton
這會導(dǎo)致什么問題呢侈沪?假設(shè)線程A已經(jīng)執(zhí)行到new Singleton()的指令2了揭璃,然后時(shí)間片到了,這時(shí)候線程B也調(diào)用getInstance();到第一個(gè)instance==null時(shí)候亭罪,直接返回了瘦馍。很開心拿到了對象了!而實(shí)際上對象還沒初始化呢皆撩!所以用了的話就是空指針了扣墩!
這就是有序性問題了!
所以一共有三大特性:可見性扛吞、原子性呻惕、有序性。并發(fā)的問題都逃不過這三大特性滥比!JAVA的內(nèi)存模型其實(shí)有很多都是解決這幾種問題的亚脆,比如上面單例的例子instance由 volatile來聲明一下,這樣就禁止指令重排了盲泛!
希望通過今天的分析大家能深入理解這三大特性濒持!寫出健壯的并發(fā)代碼键耕!