字節(jié)一面:單核 CPU 支持 Java 多線程嗎?什么?
由于現(xiàn)在大多計算機都是多核CPU,多線程往往會比單線程更快,更能夠提高并發(fā),但提高并發(fā)并不意味著啟動更多的線程來執(zhí)行。更多的線程意味著線程創(chuàng)建銷毀開銷加大、上下文非常頻繁,你的程序反而不能支持更高的TPS。
時間片
多任務(wù)系統(tǒng)往往需要同時執(zhí)行多道作業(yè)。作業(yè)數(shù)往往大于機器的CPU數(shù),然而一顆CPU同時只能執(zhí)行一項任務(wù),如何讓用戶感覺這些任務(wù)正在同時進行呢? 操作系統(tǒng)的設(shè)計者 巧妙地利用了時間片輪轉(zhuǎn)的方式
時間片是CPU分配給各個任務(wù)(線程)的時間!
“思考:單核CPU為何也支持多線程呢?
”
線程上下文是指某一時間點 CPU 寄存器和程序計數(shù)器的內(nèi)容,CPU通過時間片分配算法來循環(huán)執(zhí)行任務(wù)(線程),因為時間片非常短,所以CPU通過不停地切換線程執(zhí)行。
換言之,單CPU這么頻繁,多核CPU一定程度上可以減少上下文切換
超線程
現(xiàn)代CPU除了處理器核心之外還包括寄存器、L1L2緩存這些存儲設(shè)備、浮點運算單元、整數(shù)運算單元等一些輔助運算設(shè)備以及內(nèi)部總線等。一個多核的CPU也就是一個CPU上有多個處理器核心,就意味著程序的不同線程需要經(jīng)常在CPU之間的外部總線上通信,同時還要處理不同CPU之間不同緩存導致數(shù)據(jù)不一致的問題。
超線程這個概念是Intel提出的,簡單來說是在一個CPU上真正的并發(fā)兩個線程,由于CPU都是分時的(如果兩個線程A和B,A正在使用處理器核心,B正在使用緩存或者其他設(shè)備,那AB兩個線程就可以并發(fā)執(zhí)行,但是如果AB都在訪問同一個設(shè)備,那就只能等前一個線程執(zhí)行完后一個線程才能執(zhí)行)。實現(xiàn)這種并發(fā)的原理是 在CPU里加了一個協(xié)調(diào)輔助核心,根據(jù)Intel提供的數(shù)據(jù),這樣一個設(shè)備會使得設(shè)備面積增大5%,但是性能提高15%~30%。
上下文切換
線程切換,同一進程中的兩個線程之間的切換 進程切換,兩個進程之間的切換 模式切換,在給定線程中,用戶模式和內(nèi)核模式的切換 地址空間切換,將虛擬內(nèi)存切換到物理內(nèi)存
CPU切換前把當前任務(wù)的狀態(tài)保存下來,以便下次切換回這個任務(wù)時可以再次加載這個任務(wù)的狀態(tài),然后加載下一任務(wù)的狀態(tài)并執(zhí)行。任務(wù)的狀態(tài)保存及再加載, 這段過程就叫做上下文切換。
每個線程都有一個程序計數(shù)器(記錄要執(zhí)行的下一條指令),一組寄存器(保存當前線程的工作變量),堆棧(記錄執(zhí)行歷史,其中每一幀保存了一個已經(jīng)調(diào)用但未返回的過程)。
寄存器 是 CPU 內(nèi)部的數(shù)量較少但是速度很快的內(nèi)存(與之對應(yīng)的是 CPU 外部相對較慢的 RAM 主內(nèi)存)。寄存器通過對常用值(通常是運算的中間值)的快速訪問來提高計算機程序運行的速度。
程序計數(shù)器是一個專用的寄存器,用于表明指令序列中 CPU 正在執(zhí)行的位置,存的值為正在執(zhí)行的指令的位置或者下一個將要被執(zhí)行的指令的位置。
掛起當前任務(wù)(線程/進程),將這個任務(wù)在 CPU 中的狀態(tài)(上下文)存儲于內(nèi)存中的某處 恢復一個任務(wù)(線程/進程),在內(nèi)存中檢索下一個任務(wù)的上下文并將其在 CPU 的寄存器中恢復 跳轉(zhuǎn)到程序計數(shù)器所指向的位置(即跳轉(zhuǎn)到任務(wù)被中斷時的代碼行),以恢復該進程在程序中
線程上下文切換會有什么問題呢?
上下文切換會導致額外的開銷,常常表現(xiàn)為高并發(fā)執(zhí)行時速度會慢串行,因此減少上下文切換次數(shù)便可以提高多線程程序的運行效率。
直接消耗:指的是CPU寄存器需要保存和加載, 系統(tǒng)調(diào)度器的代碼需要執(zhí)行, TLB實例需要重新加載, CPU 的pipeline需要刷掉
間接消耗:指的是多核的cache之間得共享數(shù)據(jù), 間接消耗對于程序的影響要看線程工作區(qū)操作數(shù)據(jù)的大小
切換查看
Linux系統(tǒng)下可以使用vmstat命令來查看上下文切換的次數(shù), 其中cs列就是指上下文切換的數(shù)目(一般情況下, 空閑系統(tǒng)的上下文切換每秒大概在1500以下)

線程調(diào)度
搶占式調(diào)度
指的是每條線程執(zhí)行的時間、線程的切換都由系統(tǒng)控制,系統(tǒng)控制指的是在系統(tǒng)某種運行機制下,可能每條線程都分同樣的執(zhí)行時間片,也可能是某些線程執(zhí)行的時間片較長,甚至某些線程得不到執(zhí)行的時間片。在這種機制下,一個線程的堵塞不會導致整個進程堵塞。
java使用的線程調(diào)使用搶占式調(diào)度,Java中線程會按優(yōu)先級分配CPU時間片運行,且優(yōu)先級越高越優(yōu)先執(zhí)行,但優(yōu)先級高并不代表能獨自占用執(zhí)行時間片,可能是優(yōu)先級高得到越多的執(zhí)行時間片,反之,優(yōu)先級低的分到的執(zhí)行時間少但不會分配不到執(zhí)行時間。
協(xié)同式調(diào)度
指某一線程執(zhí)行完后主動通知系統(tǒng)切換到另一線程上執(zhí)行,這種模式就像接力賽一樣,一個人跑完自己的路程就把接力棒交接給下一個人,下個人繼續(xù)往下跑。線程的執(zhí)行時間由線程本身控制,線程切換可以預知,不存在多線程同步問題,但它有一個致命弱點:如果一個線程編寫有問題,運行到一半就一直堵塞,那么可能導致整個系統(tǒng)崩潰。
線程讓出cpu的情況
當前運行線程主動放棄CPU,JVM暫時放棄CPU操作(基于時間片輪轉(zhuǎn)調(diào)度的JVM操作系統(tǒng)不會讓線程永久放棄CPU,或者說放棄本次時間片的執(zhí)行權(quán)),例如調(diào)用 yield()方法。當前運行線程因為某些原因進入阻塞狀態(tài),例如阻塞在I/O上 當前運行線程結(jié)束,即運行完 run()方法里面的任務(wù)
引起線程上下文切換的因素
當前執(zhí)行任務(wù)(線程)的時間片用完之后,系統(tǒng)CPU正常調(diào)度下一個任務(wù) 中斷處理,在中斷處理中,其他程序”打斷”了當前正在運行的程序。當CPU接收到中斷請求時,會在正在運行的程序和發(fā)起中斷請求的程序之間進行一次上下文切換。中斷分為硬件中斷和軟件中斷,軟件中斷包括因為IO阻塞、未搶到資源或者用戶代碼等原因,線程被掛起。 用戶態(tài)切換,對于一些操作系統(tǒng),當進行用戶態(tài)切換時也會進行一次上下文切換,雖然這不是必須的。 多個任務(wù)搶占鎖資源,在多任務(wù)處理中,CPU會在不同程序之間來回切換,每個程序都有相應(yīng)的處理時間片,CPU在兩個時間片的間隔中進行上下文切換
因此優(yōu)化手段有:
無鎖并發(fā)編程,多線程處理數(shù)據(jù)時,可以用一些辦法來避免使用鎖,如將數(shù)據(jù)的ID按照Hash取模分段,不同的線程處理不同段的數(shù)據(jù) CAS算法,Java的Atomic包使用CAS算法來更新數(shù)據(jù),而不需要加鎖 使用最少線程 協(xié)程,單線程里實現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個任務(wù)間的切換
合理設(shè)置線程數(shù)目既可以最大化利用CPU,又可以減少線程切換的開銷。
高并發(fā),低耗時的情況,建議少線程。 低并發(fā),高耗時的情況:建議多線程。 高并發(fā)高耗時,要分析任務(wù)類型、增加排隊、加大線程數(shù)
來自:布道
鏈接:https://blog.csdn.net/alex_xfboy/article/details/90722654

