性能問題有哪些體現(xiàn)涛癌,什么是性能問題?
單線程不存在線程調(diào)度送火,也不存在這方面的開銷拳话,也不需要用鎖也不需要用并發(fā)數(shù)據(jù)結(jié)構(gòu),
多線程除了帶來效率的提高种吸,還帶來了安全問題弃衍,活躍性問題,性能問題骨稿。
可能造成服務(wù)響應(yīng)慢笨鸡,吞吐量低姜钳,資源消耗過高等問題坦冠。
為什么多線程會帶來性能問題?
一哥桥、調(diào)度:上下文切換
1辙浑,什么是上下文?
主要是指發(fā)生線程調(diào)度的時候
什么時候會發(fā)生線程調(diào)度拟糕?
當可運行的線程數(shù)超過了CPU核數(shù)判呕,那么操作系統(tǒng)就要調(diào)度線程了倦踢,以便讓每個線程都有機會運行,雖然最開始的操作系統(tǒng)只有進程沒有線程侠草,后來線程越來越多超過了CPU核數(shù)辱挥,就帶來了線程上下文切換以及調(diào)度的問題。
2边涕,什么是上下文切換晤碘?
假設(shè)當一個線程t1運行到Thread.sleep(),這個時候t1想進入阻塞狀態(tài)功蜓,線程調(diào)度器就會把t1阻塞园爷。然后再讓另一個等待CPU資源的線程t2進入runnable狀態(tài),這就是一次上下文切換式撼,從t1上下文切換到t2上下文童社。這種上下文切換的開銷其實非常大, 有的時候甚至比線程執(zhí)行的時間更長著隆,通常而言一次上下文切換會消耗5000~10000個CPU時鐘周期扰楼,大約是幾微秒,這對于CPU而言已經(jīng)是非常大的開銷了旅东。
3灭抑,什么是上下文?保存現(xiàn)場
與程序計數(shù)器相關(guān)的抵代,一次上下文切換腾节,主要包含以下活動:
(1)首先掛起一個線程,比如上面的t1荤牍,然后把t1線程的狀態(tài)(其實就是該線程的上下文)存在內(nèi)存中的某處案腺。一個線程上下文所包含的經(jīng)典內(nèi)容包括:該線程當前執(zhí)行到哪個指令了,位置在哪里康吵,因為后續(xù)要切換回來繼續(xù)執(zhí)行劈榨,需要跳轉(zhuǎn)到阻塞之前的狀態(tài),所以就需要保存這些信息晦嵌。還包括一些寄存器同辣,這些內(nèi)容都是為了今后我們切換回這個線程上下文后能繼續(xù)執(zhí)行。
(2)在內(nèi)存中檢索下一個進程的上下文并將其在CPU的寄存器中恢復(fù)
(3)跳轉(zhuǎn)到程序計數(shù)器所指向的位置(即跳轉(zhuǎn)到進程被中斷時的代碼行)惭载,以恢復(fù)該進程旱函。
4,緩存開銷(受上下文切換的影響)
對于調(diào)度而言我們的開銷不僅僅是上下文切換的消耗描滔,它的緩存也會帶來開銷棒妨。其實對于CPU而言我們要考慮緩存失效的問題,我們知道程序有很大概率會訪問之前訪問過的數(shù)據(jù)含长,比如for循環(huán)券腔,所以CPU為了加快運行速度伏穆,會根據(jù)不同的算法做很多預(yù)測,把不同的數(shù)據(jù)緩存到CPU中纷纫,這樣下次使用的時候很快就能取出來枕扫,但是一旦執(zhí)行了上下文切換,CPU即將執(zhí)行不同線程的不同代碼辱魁,原來的緩存就失去價值了铡原,所以CPU就需要重新進行緩存,這導(dǎo)致了線程被調(diào)度之后一開始的啟動速度比較慢商叹,因為它之前的緩存大部分都失效了燕刻。所以CPU為了防止過于頻繁的上下文切換帶來過于大的緩存開銷,通常會設(shè)置一個最小執(zhí)行時間剖笙,也就是說兩次上下文切換之間的時間間隔不能小于這個最小執(zhí)行時間卵洗,否則其實切換上下文開銷帶來的損耗都大于程序本身的執(zhí)行了。
5弥咪,何時會導(dǎo)致密集的上下文切換过蹂?搶鎖,IO
搶鎖聚至,IO或者是其他原因?qū)е铝祟l繁的線程阻塞酷勺,會帶來大量的上下文切換,有的時候我們的程序是長時間地利用CPU做計算扳躬,那么這個時候上下文切換就比較少脆诉,
二、協(xié)作:內(nèi)存同步所帶來的開銷
1贷币,我們的編譯器击胜,CPU都會幫我們把程序的指令進行優(yōu)化,可能進行指令重排序役纹,來提高我們的緩存利用率偶摔,或者JVM也會把我們的鎖進行優(yōu)化,比如它發(fā)現(xiàn)我們有些鎖是沒有必要的促脉,會自動刪除鎖辰斋。如果我們?yōu)榱硕嗑€程的安全,加了很多的synchronized瘸味,volatile這些同步手段(可見性)宫仗,在這種情況下,同步手段會使得很多的指令優(yōu)化硫戈,CPU的緩存失效(本來有緩存CPU只要去緩存中取锰什,現(xiàn)在緩存失效只能去主存中同步)下硕,其實就會影響性能丁逝。
2汁胆,關(guān)于內(nèi)存方面,由于JVM規(guī)定我們是有主內(nèi)存以及各個CPU自己的緩存的霜幼,如果我們使用緩存可以大大提高我們執(zhí)行的速度嫩码,因為不必要每次去和主內(nèi)存進行同步。但是罪既,我們使用多線程的時候經(jīng)常會使用synchronized铸题,volatile這些關(guān)鍵字,有時會讓不同線程的緩存失效琢感,這樣也會由于主內(nèi)存同步而帶來開銷丢间。
計算速度:CPU>寄存器>CPU cache>內(nèi)存>外存
T0,T1分別從主內(nèi)存中讀取數(shù)據(jù)data,拷貝一份到自己本地存儲驹针,線程t0是run在CPU上烘挫,線程會應(yīng)用到CPU本地的數(shù)據(jù)(寄存器或者cache line)來計算data,那么會產(chǎn)生線程本地數(shù)據(jù)和主內(nèi)存不一致的情況柬甥,所以在t0計算完data后要把數(shù)據(jù)寫回主存饮六。
每一次同步主內(nèi)存的數(shù)據(jù)是需要開銷的。synchronized苛蒲,volatile由于保證了可見性卤橄,所以每次要去同步主內(nèi)存?