基本概念
1.并發(fā):多個線程操作相同的資源冷冗,保證線程安全,合理利用資源
2.高并發(fā):服務(wù)能同時處理很多請求排霉,提高程序性能
3.CPU多級緩存
由來:我們知道颈走,內(nèi)存(存儲數(shù)據(jù))和CPU(處理數(shù)據(jù))是計算機(jī)的核心組件,但CPU的頻率卻遠(yuǎn)遠(yuǎn)快于內(nèi)存解滓,CPU需要等待主存從而造成了資源浪費赃磨,為了避免CPU和內(nèi)存速度不匹配的問題,緩存應(yīng)運而生
緩存的意義:(1)時間局部性:某個數(shù)據(jù)被訪問洼裤,那么在不久的將來它可能再次被訪問
(2)空間局部性:某個數(shù)據(jù)被訪問邻辉,那么與它相鄰的數(shù)據(jù)也可能被訪問
緩存一致性(MESI):
圖示多核情況下,處理器0要修改緩存行x=3腮鞍,則需如下操作:
(1)值骇、通知處理器1,處理器2我要修改此行了移国,你們的緩存行x=3變?yōu)闊o效狀態(tài)
(2)吱瘩、處理器1,處理器2接收到通知迹缀,回應(yīng)0使碾,我知道你要修改了,我放棄了x=3緩存行
(3)祝懂、處理器0這時的緩存行x=3變?yōu)楠毾頎顟B(tài)票摇,和主存一致
(4)、修改0中的x=3為x=5,變?yōu)閙odified狀態(tài)
注意:修改之前應(yīng)該為共享(shared)狀態(tài)砚蓬,shared狀態(tài)下數(shù)據(jù)是無法被修改的
起初0矢门,1,2共享狀態(tài),1颅和,2接到通知變?yōu)闊o效狀態(tài)傅事,0則變成獨享狀態(tài),緩存行修改后與主存不一致峡扩,這時則是被修改狀態(tài)。
由上分析障本,我們很容易理解下面兩幅圖示
invalid狀態(tài)為空教届,沒有保存有效數(shù)據(jù)。
注:三幅圖來自《大話處理器》
MESI消息
不同CPU的緩存之間以及主存之間的通信驾霜?
上文所述通知我要修改x=3緩存行了案训,回復(fù)我知道了這些其實就是消息和通信,載體為CPU的共享總線粪糙,下面介紹幾種MESI消息:
Read:當(dāng)CPU在自己的cache中沒有發(fā)現(xiàn)需要的物理地址强霎,就會發(fā)送一條“READ”消息,該消息包括緩存行需要讀的物理地址蓉冈。
Read Response: 是回復(fù)“Read”消息的城舞。“Read Response”消息是由內(nèi)存或者其他CPU緩存提供的寞酿。如果其他緩存請求一個處于“modified”狀態(tài)的數(shù)據(jù)家夺,則本地緩存必須提供“Read Response”消息。這個很容易理解伐弹,別的CPU在請求本地緩存中的數(shù)據(jù)拉馋,而這份數(shù)據(jù)還沒有刷新到內(nèi)存,所以必須告訴其他CPU該數(shù)據(jù)的最新值惨好。接收到”Read Response”消息后煌茴,該數(shù)據(jù)的緩存狀態(tài)就由”invalid”變成了”share”或者”exclusive”,這取決于”Read Response”的提供者是內(nèi)存還是其他CPU緩存日川。
Invalidate:“ invalidate” 消息包含要使無效的緩存行的物理地址蔓腐。其他的緩存必須從它們的緩存中移除相應(yīng)的數(shù)據(jù)并且響應(yīng)此消息。當(dāng)CPU要對一個變量進(jìn)行寫操作逗鸣,而此變量處于只讀狀態(tài)(share)合住,就需要發(fā)送“invalid”消息。由于一個變量被多個CPU緩存撒璧,所以單個CPU的改寫會造成緩存不一致透葛,所以在寫之前必須告訴其他CPU你們緩存的值馬上就要過時了。接受到”invalidate”消息的CPU就會把本地緩存中的對應(yīng)數(shù)據(jù)無效掉卿樱。
Invalidate Acknowledge:一個接收到“invalidate”消息的 CPU必須在移除指定數(shù)據(jù)后響應(yīng)一個“invalidate acknowledge”消息僚害。這個消息就是告訴“invalidate”消息的提供者“我已經(jīng)知道你要更改這個數(shù)據(jù)了,我放棄使用自己緩存中的拷貝!”
Read Invalidate:”read invalidate”消息包含要緩存行讀取的物理地址萨蚕。同時指示其他緩存移除數(shù)據(jù)靶草。因此,它包含一個”read”和一個”invalidate”岳遥∞认瑁“read invalidate”也需要“read response”以及”invalidate acknowledge”消息集。?
“Read Invalidate”消息的發(fā)送時機(jī)有兩個:第一個是CPU對一個數(shù)據(jù)進(jìn)行原子讀寫操作浩蓉,但是該數(shù)據(jù)沒有在本地CPU的緩存中派继,在其他CPU緩存中可能有該數(shù)據(jù)的拷貝。所以它需要發(fā)送一條“Read Invalidate”消息捻艳,它不僅需要讀取該數(shù)據(jù)的最新值驾窟,還要無效掉其他的CPU緩存(它馬上就要改寫該數(shù)據(jù))。
Writeback:“writeback”消息包含要回寫到內(nèi)存的地址和數(shù)據(jù)认轨。這個消息允許緩存在必要時換出“modified”狀態(tài)的數(shù)據(jù)以騰出空間绅络。消息的發(fā)送時機(jī)是,CPU把本地緩存中的數(shù)據(jù)刷新到內(nèi)存中嘁字,而該數(shù)據(jù)是share狀態(tài)(只讀)恩急,它需要告訴其他CPU”我不再使用這些緩存數(shù)據(jù)了”
CPU多級緩存亂序優(yōu)化
原理:處理器為了提高運算速度而做出違背代碼原有順序的優(yōu)化。亂序執(zhí)行初始目的是為了提高效率,但是它看來其好像在這多核時代不盡人意,其中的某些"自作聰明"的優(yōu)化導(dǎo)致多線程程序產(chǎn)生各種各樣的意外拳锚。例如兩條指令假栓,一條是準(zhǔn)備數(shù)據(jù),一條是標(biāo)識數(shù)據(jù)準(zhǔn)備完成霍掺,這種在多核情況下是不安全的匾荆。
java內(nèi)存模型(JMM)
規(guī)定了一個線程如何和何時可以看到由其他線程修改后的共享變量的值,以及在必須時如何同步的訪問共享變量杆烁。
如圖牙丽,對象的引用存放在線程棧上,但是這個對象卻是存放在堆當(dāng)中兔魂,如果兩個線程同時調(diào)用同一個對象的同一個方法烤芦,他們將都可以訪問這個對象的成員變量,但是每一個線程都擁有了這個成員變量的私有拷貝析校。
速度:寄存器>高速緩存>緩存
線程A和線程B如何通信构罗?
1、線程A將改變后的共享變量的值更新到住內(nèi)存當(dāng)中
2智玻、線程B從主內(nèi)存讀取由線程A刷新之后的值
*這就是為什么多線程操作同一個變量時是線程不安全的遂唧,線程A尚未將更新后的值刷新回主內(nèi)存,線程B就從主內(nèi)存讀取了共享變量的值吊奢,讀取到了錯誤的數(shù)據(jù)盖彭,從而導(dǎo)致最終的結(jié)果不對。從而引入了同步手段。
java內(nèi)存模型同步操作與規(guī)則
(1)lock(鎖定):作用于主內(nèi)存的變量召边,把一個變量標(biāo)記為一條線程獨占狀態(tài)
(2)unlock(解鎖):作用于主內(nèi)存的變量铺呵,把一個處于鎖定狀態(tài)的變量釋放出來,釋放后的變量才可以被其他線程鎖定
(3)read(讀取):作用于主內(nèi)存的變量隧熙,把一個變量值從主內(nèi)存?zhèn)鬏數(shù)骄€程的工作內(nèi)存中片挂,以便隨后的load動作使用
(4)load(載入):作用于工作內(nèi)存的變量,它把read操作從主內(nèi)存中得到的變量值放入工作內(nèi)存的變量副本中
(5)use(使用):作用于工作內(nèi)存的變量贞盯,把工作內(nèi)存中的一個變量值傳遞給執(zhí)行引擎
(6)assign(賦值):作用于工作內(nèi)存的變量宴卖,它把一個從執(zhí)行引擎接收到的值賦給工作內(nèi)存的變量
(7)store(存儲):作用于工作內(nèi)存的變量,把工作內(nèi)存中的一個變量的值傳送到主內(nèi)存中邻悬,以便隨后的write的操作
(8)write(寫入):作用于工作內(nèi)存的變量,它把store操作從工作內(nèi)存中的一個變量的值傳送到主內(nèi)存的變量中
如果要把一個變量從主內(nèi)存中復(fù)制到工作內(nèi)存中随闽,就需要按順序地執(zhí)行read和load操作父丰,如果把變量從工作內(nèi)存中同步到主內(nèi)存中,就需要按順序地執(zhí)行store和write操作掘宪。但Java內(nèi)存模型只要求上述操作必須按順序執(zhí)行蛾扇,而沒有保證必須是連續(xù)執(zhí)行
同步規(guī)則分析:
1)不允許一個線程無原因地(沒有發(fā)生過任何assign操作)把數(shù)據(jù)從工作內(nèi)存同步會主內(nèi)存中
2)一個新的變量只能在主內(nèi)存中誕生,不允許在工作內(nèi)存中直接使用一個未被初始化(load或者assign)的變量魏滚。即就是對一個變量實施use和store操作之前镀首,必須先自行assign和load操作。
3)一個變量在同一時刻只允許一條線程對其進(jìn)行l(wèi)ock操作鼠次,但lock操作可以被同一線程重復(fù)執(zhí)行多次更哄,多次執(zhí)行l(wèi)ock后,只有執(zhí)行相同次數(shù)的unlock操作腥寇,變量才會被解鎖成翩。lock和unlock必須成對出現(xiàn)。
4)如果對一個變量執(zhí)行l(wèi)ock操作赦役,將會清空工作內(nèi)存中此變量的值麻敌,在執(zhí)行引擎使用這個變量之前需要重新執(zhí)行l(wèi)oad或assign操作初始化變量的值。
5)如果一個變量事先沒有被lock操作鎖定掂摔,則不允許對它執(zhí)行unlock操作术羔;也不允許去unlock一個被其他線程鎖定的變量。
6)對一個變量執(zhí)行unlock操作之前乙漓,必須先把此變量同步到主內(nèi)存中(執(zhí)行store和write操作)
總結(jié):
并發(fā)有什么問題级历?
(1)安全性:多個線程共享數(shù)據(jù)時可能會產(chǎn)生于期望不相符的結(jié)果(未做同步操作的情況下)
(2)活躍性:某個操作無法繼續(xù)進(jìn)行下去時,就會發(fā)生活躍性問題簇秒。比如:死鎖鱼喉、饑餓等問題
(3)性能:線程過多時會使得:CPU頻繁切換,調(diào)度時間增多;同步機(jī)制扛禽;消耗過多內(nèi)存锋边,任何事情都不是絕對的,多線程既能提高性能也可能降低性能编曼,關(guān)鍵是要結(jié)合實際場景豆巨,看瓶頸到底在哪里?一味提高線程數(shù)肯定是不對的掐场。
什么時候考慮多線程往扔?
(1)速度:同時處理多個請求,響應(yīng)更快熊户;復(fù)雜的操作可以分成多個進(jìn)程(或線程)同時進(jìn)行萍膛,可以簡單理解為異步請求,不必等待此次進(jìn)程的結(jié)果嚷堡,繼續(xù)往下執(zhí)行蝗罗。
(2)設(shè)計:程序設(shè)計在某些情況下更簡單,也可以有更多的選擇
(3)資源利用:CPU能夠等待IO的時候能夠做一些其他的事情蝌戒,這是從成本和速度兩個角度出發(fā)的串塑,系統(tǒng)卡慢的時候多從程序內(nèi)部去分析,只是橫向增加機(jī)器的性能是沒用的北苟,機(jī)器再好桩匪,你始終是單線程,或者說始終只使用了資源的百分之一友鼻,這樣既浪費了資源傻昙,也不能有效的提高響應(yīng)的速度。