衡量一個(gè)服務(wù)性能的高低好壞,每秒事務(wù)處理數(shù)(Transactions Per Second惠呼,TPS與QPS類似)是最重要的指標(biāo)之一导俘。
1、硬件效率與緩存一致性
絕大多數(shù)任務(wù)不可能只靠處理器計(jì)算剔蹋,處理器至少需要與內(nèi)存交互旅薄,如讀取運(yùn)算數(shù)據(jù)、存儲運(yùn)算結(jié)果滩租,這些I/O操作很難消除赋秀,由于計(jì)算機(jī)的存儲設(shè)備與處理器的運(yùn)算速度存在幾個(gè)數(shù)量級的差距,故計(jì)算機(jī)系統(tǒng)加了一層讀寫速度盡可能接近處理器運(yùn)算速度的高速緩存來作為內(nèi)存與處理器之間的緩沖律想×粤基于高速緩存的存儲交互解決了處理器與內(nèi)存的速度矛盾,同時(shí)引入了一個(gè)新的問題:緩存一致性(Cache Coherence)技即,每個(gè)處理器都有自己的緩沖著洼,同時(shí)又共享同一主內(nèi)存,當(dāng)多個(gè)處理器運(yùn)算任務(wù)涉及到同一塊內(nèi)存區(qū)域而叼,可能導(dǎo)致數(shù)據(jù)不一致情況身笤。此時(shí)需要各個(gè)處理器訪問緩存時(shí)遵循一些協(xié)議,這類協(xié)議有:MSI葵陵、MESI液荸、MOSI、Synapse脱篙、Firefly娇钱、Dragon Protocol。除增加高速緩存外绊困,處理器可能會對輸入的代碼進(jìn)行亂序執(zhí)行優(yōu)化文搂,在計(jì)算之后將亂序執(zhí)行結(jié)果重組,保證與順序執(zhí)行結(jié)果一致秤朗。
2煤蹭、Java內(nèi)存模型(Java Memory Modal,JMM)
Java內(nèi)存模型的主要目標(biāo)是定義程序中各個(gè)變量的訪問規(guī)則取视,即在虛擬機(jī)中將變量存儲到內(nèi)存和從內(nèi)存中取出變量這樣的底層細(xì)節(jié)硝皂。JMM規(guī)定了所有變量都存儲在主內(nèi)存中,每條線程有自己的工作內(nèi)存贫途,工作內(nèi)存保存了主內(nèi)存變量的副本拷貝吧彪,變量的讀取復(fù)制操作必須在工作內(nèi)存中進(jìn)行,線程間變量的傳遞需要通過主內(nèi)存來完成丢早。
變量的拷貝和同步,JMM定義了8種操作來完成:
lock(鎖定):作用于主內(nèi)存變量,標(biāo)識為一條線程獨(dú)占
unlock(解鎖):作用于主內(nèi)存變量怨酝,釋放鎖定狀態(tài)
read(讀取):作用于主內(nèi)存變量傀缩,將變量的值從主內(nèi)存?zhèn)鬏數(shù)焦ぷ鲀?nèi)存,以便隨后load動作作用
load(載入):作用于工作內(nèi)存农猬,將read操作的變量放入工作內(nèi)存副本當(dāng)中
use(使用):作用于工作內(nèi)存赡艰,將變量的值傳遞給執(zhí)行引擎,虛擬機(jī)遇到使用變量的值時(shí)都會有這個(gè)操作
assign(賦值):作用于工作內(nèi)存斤葱,把從執(zhí)行引擎接收到的值賦給工作內(nèi)存變量
store(存儲):作用于工作內(nèi)存慷垮,將工作中內(nèi)存變量傳遞給主內(nèi)存,以便write操作
write(寫入):作用于主內(nèi)存揍堕,把store操作從工作內(nèi)存的變量值放入主內(nèi)存變量中
由此可見料身,read\load成對出現(xiàn)、store/write成對出現(xiàn)衩茸,JMM還規(guī)定了執(zhí)行上述操作需要滿足的規(guī)則:
①芹血、不允許read和load,store和write單獨(dú)出現(xiàn)
②楞慈、不允許線程丟棄最近的assign操作幔烛,規(guī)定變量改變之后必須同步到主內(nèi)存
③、不允許線程無緣由地從工作線程同步到主內(nèi)存(需要執(zhí)行assign之后)
④囊蓝、use饿悬、store之前必須要執(zhí)行assig、load聚霜,新變量只能在主內(nèi)存中產(chǎn)生
⑤狡恬、一個(gè)變量同一時(shí)刻只能由一個(gè)線程進(jìn)行l(wèi)ock,多次lock需要對應(yīng)此時(shí)unlock俯萎,變量才會被解鎖
⑥傲宜、對一個(gè)變量執(zhí)行了lock操作,會清空工作內(nèi)存此變量的值夫啊。在執(zhí)行引擎使用前需要重新load或assign
⑦函卒、執(zhí)行unlock之前,必須將此變量同步會主內(nèi)存中
3撇眯、volatile變量特殊規(guī)則:
①保存此變量對所有線程可見性报嵌。但在并發(fā)下一樣不安全。volatile變量在各個(gè)線程的工作內(nèi)存中存在不一致性問題熊榛,但當(dāng)執(zhí)行引擎每次使用锚国,都會進(jìn)行刷新,也就可認(rèn)為不存在一致性問題玄坦。由于java的運(yùn)算并非原子操作血筑,volatile變量在并發(fā)情況下并不是安全的(A線程使用前已經(jīng)刷新绘沉,此時(shí)B線程將新值賦給工作內(nèi)存,此時(shí)A計(jì)算的數(shù)據(jù)就不是安全的)豺总。
②禁止指令重排序车伞,首先指令重排序是指CPU采用了允許將多條指令不按程序規(guī)定的順序分開發(fā)送給各相應(yīng)的電路單元處理,但并不是指令任意排序喻喳,CPU需要正確處理指令依賴情況以保障程序得到正確的執(zhí)行結(jié)果另玖。禁止指令重排序通過設(shè)置內(nèi)存屏障來實(shí)現(xiàn),指令重排序不能把后面的指令重排序到內(nèi)存屏障之前的位置表伦。
volatile變量的讀性能與普通變量幾乎沒什么差別谦去,但寫操作會慢一些,需要在本地代碼插入許多內(nèi)存屏障指令來保證處理器不發(fā)生亂序執(zhí)行蹦哼。即便如此鳄哭,大多數(shù)volatile的總開銷要比鎖低。
4翔怎、先行發(fā)生原則(happens-before)窃诉,判斷數(shù)據(jù)是否存在競爭、線程是否安全的主要依據(jù)赤套。如果說A操作先行發(fā)生于B操作飘痛,那么在B操作發(fā)生之前,A操作的影響能被B操作觀察到容握,“影響”包括修改內(nèi)存中共享變量的值宣脉、發(fā)送了消息、調(diào)用了方法等剔氏。
①塑猖、程序次序規(guī)則,一個(gè)線程內(nèi)按程序代碼順序執(zhí)行谈跛,前面的代碼先于后面的代碼羊苟。
②、管程鎖定規(guī)則感憾,一個(gè)unlock操作先于后面的lock操作
③蜡励、volatile規(guī)則,volatile前面的寫操作先于后面的讀操作
④阻桅、線程啟動規(guī)則凉倚,start()方法先于此線程的每一個(gè)動作
⑤、線程終止規(guī)則嫂沉,線程中所有操作都先于對此線程的終止檢測稽寒,Thread.join(),Thread.isAlive()檢測線程是否終止
⑥趟章、線程中斷規(guī)則杏糙,對線程interrup()方法調(diào)用先行被中斷線程檢測到中斷事件的發(fā)生慎王,通過Thread.interrupted檢測是否有中斷
⑦、對象終結(jié)規(guī)則搔啊,一個(gè)對象的初始化完成先行于finalize()方法
⑧柬祠、傳遞性北戏,A先行于B负芋,B先行于C,則A先行于C嗜愈。
5旧蛾、線程
線程是比進(jìn)程更輕量級的調(diào)度執(zhí)行單位,線程的引入蠕嫁,可以把一個(gè)進(jìn)程的資源分配和執(zhí)行調(diào)度分開锨天,各個(gè)線程既可以共享進(jìn)程資源,又可以獨(dú)立調(diào)度剃毒。
實(shí)現(xiàn)進(jìn)程的方式:內(nèi)核線程實(shí)現(xiàn)病袄、用戶線程實(shí)現(xiàn)和用戶線程+輕量級進(jìn)程混合實(shí)現(xiàn)。
①赘阀、內(nèi)核線程實(shí)現(xiàn)益缠,這種線程由內(nèi)核來完成線程切換,這種線程由叫輕量級進(jìn)程基公,每個(gè)輕量級進(jìn)程都由一個(gè)內(nèi)核線程支持幅慌,比例1:1。每個(gè)輕量級進(jìn)程都可作為一個(gè)獨(dú)立的調(diào)度單元轰豆,即使阻塞了胰伍,也不會影響進(jìn)程繼續(xù)工作∷嵝荩基于內(nèi)核實(shí)現(xiàn)骂租,故線程的創(chuàng)建、析構(gòu)斑司、同步渗饮,都需要進(jìn)行系統(tǒng)調(diào)用,需要在用戶態(tài)和內(nèi)核態(tài)之間頻繁轉(zhuǎn)換陡厘。
②抽米、用戶線程實(shí)現(xiàn),廣義上不屬于內(nèi)核線程糙置,就是用戶線程云茸,輕量級進(jìn)程也屬于用戶線程。狹義上谤饭,完全建立在用戶空間的線程才是用戶線程标捺,系統(tǒng)內(nèi)核不感知線程實(shí)現(xiàn)懊纳。線程的建立、同步亡容、銷毀和調(diào)度完全在用戶態(tài)中完成嗤疯。因而這類線程快速且低消耗,可支持更大規(guī)模的線程數(shù)量闺兢,部分高性能數(shù)據(jù)庫中多線程即用戶線程實(shí)現(xiàn)茂缚,進(jìn)程與用戶線程比例為1:N。但因操作系統(tǒng)只把資源分配到進(jìn)程屋谭,而線程的創(chuàng)建脚囊、切換、調(diào)度都需要考慮桐磁,卻類似阻塞悔耘、多處理器中線程映射到其他處理器等問題幾乎無法處理。
③我擂、用戶線程+輕量級進(jìn)程衬以,用戶進(jìn)程任然建立在用戶空間,操作系統(tǒng)提供的輕量級進(jìn)程作為用戶線程和內(nèi)核線程溝通的橋梁校摩,大大降低了進(jìn)程被完全阻塞的風(fēng)險(xiǎn)看峻。用戶線程與輕量級進(jìn)程比例為N:M。
Sun JDK中秧耗,Windows版本與Linux版本都是使用1:1線程模型實(shí)現(xiàn)备籽。
6、線程調(diào)度指系統(tǒng)為線程分配處理器使用權(quán)的過程分井,主要有協(xié)同式線程調(diào)度和搶占式線程調(diào)度车猬。
①、協(xié)同式線程調(diào)度尺锚,線程的執(zhí)行時(shí)間由線程本身調(diào)度珠闰,工作執(zhí)行完之后,主動通知系統(tǒng)切換到另一個(gè)線程上瘫辩。導(dǎo)致線程執(zhí)行時(shí)間不可控伏嗜,一直不通知系統(tǒng)切換,程序就一直阻塞伐厌。
②承绸、搶占式線程調(diào)度,每個(gè)線程由系統(tǒng)分配執(zhí)行時(shí)間挣轨,線程切換不由程序本身決定(Thread.yield()只能讓出cpu時(shí)間军熏,無法獲取執(zhí)行時(shí)間),java線程就使用搶占式調(diào)度卷扮,同時(shí)可設(shè)置優(yōu)先級(10個(gè)級別)建議系統(tǒng)給某些線程多分配一點(diǎn)執(zhí)行時(shí)間荡澎。
7均践、線程狀態(tài):
①、新建(New):創(chuàng)建后尚未啟動摩幔。
②彤委、運(yùn)行(Runable):包括操作系統(tǒng)線程狀態(tài)中的Runing和Ready,即可能正在運(yùn)行或衡,也可能正在等待分配CPU時(shí)間焦影。
③、無限等待(Waiting):這種狀態(tài)的線程不會被分配CPU時(shí)間薇宠,需要等待其他線程顯式地喚醒(Object.wait() Thread.join() LockSupport.park())偷办。
④、限期等待(Time Waiting):這種狀態(tài)線程也不會被分配CPU時(shí)間澄港,但不需要等待其他線程顯式地喚醒,一定時(shí)間會由系統(tǒng)自動喚醒(Thread.sleep() Object.wait(time) Thread.join(time) LockSupport.parkNanos() LockSupport.parkUnit())柄沮。
⑤回梧、阻塞(Blocked):阻塞與等待的區(qū)別在于,阻塞在等待獲取一個(gè)排它鎖祖搓,而等待狀態(tài)則等待一段時(shí)間或者喚醒動作發(fā)生狱意。
⑥、結(jié)束(Terminated):已終止線程狀態(tài)拯欧,線程已經(jīng)執(zhí)行結(jié)束详囤。
8、線程安全:當(dāng)多個(gè)線程訪問一個(gè)對象時(shí)镐作,不用考慮線程在運(yùn)行時(shí)的調(diào)度和交替執(zhí)行藏姐,也不需要進(jìn)行額外的同步,或其他的協(xié)調(diào)操作该贾,調(diào)用這個(gè)對象的行為都可以獲得正確的結(jié)果羔杨,那么這個(gè)對象就是線程安全的。java語言中的線程安全:不可變杨蛋、絕對線程安全兜材、相對線程安全、線程兼容和線程對立逞力。
①曙寡、不可變:final關(guān)鍵字,只要一個(gè)不可變對象被構(gòu)建出來寇荧,就不會改變举庶。(this引用逃逸情況例外,如在構(gòu)造方法還未初始化內(nèi)部靜態(tài)屬性砚亭,就已經(jīng)被其他線程使用灯变,有可能造成NPE)
②殴玛、絕對線程安全:如Vector,所有的方法都添加了synchronized添祸,但是不經(jīng)調(diào)控滚粟,有可能拋出錯(cuò)誤
③、相對線程安全:如Vector刃泌、HashTable等
④凡壤、線程兼容:需要使用同步手段實(shí)現(xiàn)線程安全
⑤、線程對立:無論是否采取同步措施耙替,都無法在多線程環(huán)境中并發(fā)使用亚侠,如Thread類的suspend()和resume()
9、鎖優(yōu)化:
①俗扇、自旋鎖:掛起線程和恢復(fù)線程都需要轉(zhuǎn)入內(nèi)核態(tài)完成硝烂,這些操作為并發(fā)帶來了很大的壓力。很大共享數(shù)據(jù)的鎖定狀態(tài)只會持續(xù)很短一段時(shí)間铜幽,讓請求鎖的線程暫不放棄CPU時(shí)間滞谢,而進(jìn)行一個(gè)忙循環(huán),等待鎖釋放除抛。
②狮杨、鎖消除:指虛擬機(jī)即時(shí)編輯器在運(yùn)行時(shí),對一些代碼上要求同步到忽,但被檢測到不可能存在共享數(shù)據(jù)競爭的鎖進(jìn)行消除橄教。
③、鎖粗化:在存在鎖競爭是一般推薦將同步代碼塊限制盡量小喘漏,為了線程盡可能拿到鎖护蝶。但當(dāng)一系列操作都對同一個(gè)對象反復(fù)加鎖和解鎖,則導(dǎo)致不必要的開銷陷遮,存在這種情況一般會將范圍擴(kuò)展到整個(gè)操作序列滓走,實(shí)現(xiàn)一次加解鎖。
④帽馋、輕量級鎖:HotSpot虛擬機(jī)的對象頭分兩部分信息搅方,其中一部分存儲對象自身的運(yùn)行時(shí)數(shù)據(jù)(另一部分時(shí)類元數(shù)據(jù)),如哈希碼绽族、GC分代年齡等姨涡,稱為Mark Word,是實(shí)現(xiàn)輕量級鎖的關(guān)鍵吧慢。輕量級鎖的依據(jù)是對于絕大部分鎖涛漂,在整個(gè)同步周期內(nèi)都不存在競爭。輕量級鎖的上鎖過程:如果對象沒有被鎖定,虛擬機(jī)在棧幀上建立一個(gè)所記錄(Lock Record)匈仗,存儲當(dāng)前對象的Mark Word拷貝瓢剿,然后虛擬機(jī)嘗試CAS操作將對象Mark Word更新為指向Lock Record的指針。如果操作失敗悠轩,先檢查是否已經(jīng)在當(dāng)前棧幀间狂,如果在可直接進(jìn)入同步代碼塊,否則已經(jīng)被其他線程搶占火架。如果有兩條以上的線程競爭同一個(gè)鎖鉴象,輕量級鎖就不再有效,要膨脹為重量級鎖何鸡,等待鎖的線程會進(jìn)入阻塞狀態(tài)纺弊。
⑤、偏向鎖:當(dāng)鎖對象第一次被線程獲取,即設(shè)置為偏向模式(01),同時(shí)使用CAS操作把獲取這個(gè)鎖的線程ID記錄在Mark Word中,操作成功,后續(xù)每次進(jìn)入同步塊時(shí)馁启,都不必進(jìn)行同步操作。如果另一個(gè)線程嘗試獲取鎖秕磷,偏向模式結(jié)束曲秉,恢復(fù)到未鎖定或輕量級鎖。