進(jìn)程與線程
進(jìn)程是程序執(zhí)行的最小單位
線程是cpu調(diào)度的最小單位
多線程硬件模型
讀與返回的過(guò)程比較緩慢啃匿,通過(guò)總線
L1、L2、L3三級(jí)緩存览效,速度依次減,容量依次加
計(jì)算單元要讀取數(shù)據(jù)順序?yàn)長(zhǎng)1虫几、L2锤灿、L3、內(nèi)存
可見(jiàn)性問(wèn)題
內(nèi)存有數(shù)據(jù)count=1辆脸,cpu0但校、cpu1把內(nèi)存中的count緩存到本地,都要做count++每强,此時(shí)都返回給內(nèi)存始腾,兩遍count++最終結(jié)果卻是count=2
方案:
- 總線上鎖:變成串行,多線程沒(méi)意義了
- 數(shù)據(jù)一致性的緩存鎖
緩存鎖
MESI協(xié)議(修改空执、獨(dú)占浪箭、共享、失效)
- cpu0辨绊、cpu1都加載count到自己緩存奶栖,本質(zhì)上都是主存里count的副本,count為S狀態(tài)
- cpu0想count++门坷,cpu0將本地的count改成M狀態(tài)宣鄙,cpu0通知所有有這個(gè)count副本的其他線程(如cpu1),使其失效
- cpu1接到通知的把本地count改成I狀態(tài)并且把修改結(jié)果返回給cpu0
- cpu0收到返回后把count++后的新結(jié)果返回給主存默蚌,把本地count改成E狀態(tài)
- cpu1這時(shí)要做count++會(huì)發(fā)現(xiàn)本地count變成I狀態(tài)冻晤,需要到主存中取
M-E狀態(tài)之間,cpu類似阻塞绸吸;寫操作放入storebuffer鼻弧、讀操作放入loadbuffer
那什么樣的數(shù)據(jù)需要緩存设江?緩存中數(shù)據(jù)長(zhǎng)什么樣呢?
局部性原理
時(shí)間局部性:主存中有x攘轩,cpu加載x到緩存叉存,認(rèn)為接下來(lái)的操作都需要用到x
空間局部性:兩個(gè)數(shù)據(jù)(x,y)在主存中空間上挨在一起度帮,cpu需要用到x歼捏,但認(rèn)為接下來(lái)的操作也需要用到y(tǒng)。加載的時(shí)候就把(x笨篷,y)都加載到緩存行(64字節(jié))瞳秽。由于存在MESI協(xié)議,導(dǎo)致x冕屯,y互相鎖寂诱,即偽共享。
偽共享
可以在代碼部分解決
第二種其實(shí)在優(yōu)化偽共享安聘,long8個(gè)字節(jié)痰洒,前后加8個(gè)long隔離,避免走M(jìn)ESI協(xié)議浴韭,影響效率
解決
硬件層面丘喻,即cpu處理器上,有memorybarrier
軟件層面念颈,JVM規(guī)范匯總泉粉,有四種內(nèi)存屏障(LoadLoadBarries、storestore榴芳、loadstore嗡靡、storeload)->JMM模型(JVM中的一種規(guī)范,規(guī)范了java虛擬機(jī)和計(jì)算機(jī)內(nèi)存如何協(xié)同共存)
JMM模型
JMM指令
lock作用于主內(nèi)存窟感,標(biāo)記變量只能被一條線程讀取
read讨彼、load不能單獨(dú)使用,read+load將變量加載到高速緩存中來(lái)
JVM層面完成了以上這些指令柿祈,用java線程和內(nèi)存完成讀寫操作哈误,并且加上一些內(nèi)存屏障
volatile、sync躏嚎、final關(guān)鍵字
順序性問(wèn)題
指令重排序
由于有storebuffer存在蜜自,buffer的存在使得一些寫操作在storebuffer中進(jìn)行,而一些寫操作是直接寫到內(nèi)存中卢佣,無(wú)法保證storebuffer重荠、內(nèi)存里指令的順序
解決
voliatle、sync
JAVA內(nèi)存模型(多線程軟件模型)
JMM模型
規(guī)范四種內(nèi)存屏障LoadLoadBarries虚茶、StoreStoreLoadStore戈鲁、StoreLoad
lock尾膊、sync、volatile實(shí)現(xiàn)了內(nèi)存屏障
多線程三大核心性質(zhì)
原子性荞彼、可見(jiàn)性、順序性
Voliate(無(wú)法保證原子性)
本質(zhì):讀前插入讀屏障待笑,寫前插入寫屏障鸣皂,就可以解決可見(jiàn)性還有指令重排問(wèn)題
源碼
OrderAccess:storeload:萬(wàn)能屏障,即把寫緩存數(shù)據(jù)全都刷新到內(nèi)存中
linux86中storeload實(shí)現(xiàn):
is_MP:是否是多cpu(多線程)
主要執(zhí)行l(wèi)ock暮蹂,鎖住了0+0(語(yǔ)法規(guī)定寞缝,沒(méi)什么實(shí)際意義)
為什么無(wú)法保證原子性
j++在底層是3條指令,讀取——>加——>寫入
而volatile是指令和指令之間加上內(nèi)存屏障仰泻,所以無(wú)法保證原子性
new操作包含了4條指令
單例模式
多線程情況下荆陆,同時(shí)判斷INSTANCE==null,就不是單例了
如何修改集侯?
加鎖
但是這個(gè)鎖加的范圍太大了被啼,進(jìn)一步優(yōu)化
但這仍然有線程安全問(wèn)題,同時(shí)會(huì)認(rèn)為INSTANCE==null棠枉,鎖住了但不影響另一個(gè)往下走
再加一層判斷
雙重檢查鎖單例模式浓体。注意仍然具有線程安全問(wèn)題。
INSTANCE關(guān)鍵字需不需要加上voliatle呢辈讶?
需要命浴。因?yàn)樽⒁鈔ew這步其實(shí)是4個(gè)指令,防止指令重排序