在Java語言中般哼,除了基本數(shù)據(jù)類型外把敢,其他的都是指向各類對象的對象引用;Java中根據(jù)其生命周期的長短燥爷,將引用分為4類蜈亩。
1 強引用
特點:我們平常典型編碼Object obj = new Object()中的obj就是強引用。通過關鍵字new創(chuàng)建的對象所關聯(lián)的引用就是強引用前翎。 當JVM內(nèi)存空間不足稚配,JVM寧愿拋出OutOfMemoryError運 行時錯誤(OOM),使程序異常終止港华,也不會靠隨意回收具有強引用的“存活”對象來解決內(nèi)存不足的問題道川。對于一個普通的對象,如果沒有其他的引用關系立宜,只要超過了引用的作用域或者顯式 地將相應(強)引用賦值為 null冒萄,就是可以被垃圾收集的了,具體回收時機還是要看垃圾收集策略橙数。
2 軟引用
特點:軟引用通過SoftReference類實現(xiàn)尊流。 軟引用的生命周期比強引用短一些。只有當 JVM 認為內(nèi)存不足時灯帮,才會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 之前奠旺,清理軟引用指向的對象蜘澜。軟引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用,如果軟引用所引用的對象被垃圾回收器回收响疚,Java虛擬機就會把這個軟引用加入到與之關聯(lián)的引用 隊列中。后續(xù)瞪醋,我們可以調(diào)用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收忿晕。如果隊列為空,將返回一個null,否則該方法返回隊列中前面的一個Reference對象银受。
應用場景:軟引用通常用來實現(xiàn)內(nèi)存敏感的緩存践盼。如果還有空閑內(nèi)存,就可以暫時保留緩存宾巍,當內(nèi)存不足時清理掉咕幻,這樣就保證了使用緩存的同時,不會耗盡內(nèi)存顶霞。
3 弱引用
弱引用通過WeakReference類實現(xiàn)肄程。 弱引用的生命周期比軟引用短。在垃圾回收器線程掃描它所管轄的內(nèi)存區(qū)域的過程中选浑,一旦發(fā)現(xiàn)了具有弱引用的對象蓝厌,不管當前內(nèi)存空間足夠與否,都會 回收它的內(nèi)存古徒。由于垃圾回收器是一個優(yōu)先級很低的線程拓提,因此不一定會很快回收弱引用的對象。弱引用可以和一個引用隊列(ReferenceQueue)聯(lián)合使用隧膘,如果弱引用所引用的對象被垃圾 回收代态,Java虛擬機就會把這個弱引用加入到與之關聯(lián)的引用隊列中。
應用場景:弱應用同樣可用于內(nèi)存敏感的緩存疹吃。
4 虛引用
特點:虛引用也叫幻象引用蹦疑,通過PhantomReference類來實現(xiàn)。無法通過虛引用訪問對象的任何屬性或函數(shù)互墓”啬幔幻象引用僅僅是提供了一種確保對象被 fnalize 以后,做某些事情的機制篡撵。如果 一個對象僅持有虛引用判莉,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收器回收育谬。虛引用必須和引用隊列 (ReferenceQueue)聯(lián)合使用券盅。當垃圾回收器準備回收一個對象時,如 果發(fā)現(xiàn)它還有虛引用膛檀,就會在回收對象的內(nèi)存之前护盈,把這個虛引用加入到與之關聯(lián)的引用隊列中舍肠。
ReferenceQueue queue = new ReferenceQueue ();
PhantomReference pr = new PhantomReference (object, queue);
程序可以通過判斷引用隊列中是否已經(jīng)加入了虛引用技矮,來了解被引用的對象是否將要被垃圾回收。如果程序發(fā)現(xiàn)某個虛引用已經(jīng)被加入到引用隊列憾筏,那么就可以在所引用的對象的內(nèi)存被回收之
前采取一些程序行動。
應用場景:可用來跟蹤對象被垃圾回收器回收的活動花鹅,當一個虛引用關聯(lián)的對象被垃圾收集器回收之前會收到一條系統(tǒng)通知氧腰。
1 String
(1) String的創(chuàng)建機理 由于String在Java世界中使用過于頻繁,Java為了避免在一個系統(tǒng)中產(chǎn)生大量的String對象刨肃,引入了字符串常量池古拴。其運行機制是:創(chuàng)建一個字符串時,首先檢查池中是否有值相同的字符串對象真友,如果有則不需要創(chuàng)建直接從池中剛查找到的對象引用;如果沒有則新建字符串對象黄痪,返回對象引用,并且將新創(chuàng)建的對象放入池中盔然。但是桅打,通過new方法創(chuàng)建的String對象是不檢查字符串池的,而是直接在堆區(qū)或棧區(qū)創(chuàng)建一個新的對象轻纪,也不會把對象放入池中油额。上述原則只適用于通過直接量給String對象引用賦值的情況。
舉例:String str1 = "123"; //通過直接量賦值方式刻帚,放入字符串常量池
String str2 = new String(“123”);//通過new方式賦值方式潦嘶,不放入字符串常量池
注意:String提供了inter()方法。調(diào)用該方法時崇众,如果常量池中包括了一個等于此String對象的字符串(由equals方法確定)掂僵,則返回池中的字符串。否則顷歌,將此String對象添加到池中锰蓬,并且 返回此池中對象的引用。
(2) String的特性
[A] 不可變眯漩。是指String對象一旦生成芹扭,則不能再對它進行改變。不可變的主要作用在于當一個對象需要被多線程共享赦抖,并且訪問頻繁時舱卡,可以省略同步和鎖等待的時間,從而大幅度提高系統(tǒng) 性能队萤。不可變模式是一個可以提高多線程程序的性能轮锥,降低多線程程序復雜度的設計模式。
[B] 針對常量池的優(yōu)化要尔。當2個String對象擁有相同的值時舍杜,他們只引用常量池中的同一個拷貝新娜。當同一個字符串反復出現(xiàn)時,這個技術可以大幅度節(jié)省內(nèi)存空間既绩。
2 StringBufer/StringBuilder
StringBufer和StringBuilder都實現(xiàn)了AbstractStringBuilder抽象類概龄,擁有幾乎一致對外提供的調(diào)用接口;其底層在內(nèi)存中的存儲方式與String相同,都是以一個有序的字符序列(char類型 的數(shù)組)進行存儲熬词,不同點是StringBufer/StringBuilder對象的值是可以改變的旁钧,并且值改變以后,對象引用不會發(fā)生改變;兩者對象在構造過程中互拾,首先按照默認大小申請一個字符數(shù)組,由 于會不斷加入新數(shù)據(jù)嚎幸,當超過默認大小后颜矿,會創(chuàng)建一個更大的數(shù)組,并將原先的數(shù)組內(nèi)容復制過來嫉晶,再丟棄舊的數(shù)組骑疆。因此,對于較大對象的擴容會涉及大量的內(nèi)存復制操作替废,如果能夠預先評 估大小箍铭,可提升性能。
唯一需要注意的是:StringBufer是線程安全的椎镣,但是StringBuilder是線程不安全的诈火。可參看Java標準類庫的源代碼状答,StringBufer類中方法定義前面都會有synchronize關鍵字冷守。為 此,StringBufer的性能要遠低于StringBuilder惊科。
3 應用場景
[A]在字符串內(nèi)容不經(jīng)常發(fā)生變化的業(yè)務場景優(yōu)先使用String類拍摇。例如:常量聲明、少量的字符串拼接操作等馆截。如果有大量的字符串內(nèi)容拼接充活,避免使用String與String之間的“+”操作,因為這
樣會產(chǎn)生大量無用的中間對象蜡娶,耗費空間且執(zhí)行效率低下(新建對象混卵、回收對象花費大量時間)。
[B]在頻繁進行字符串的運算(如拼接翎蹈、替換淮菠、刪除等),并且運行在多線程環(huán)境下荤堪,建議使用StringBufer合陵,例如XML解析枢赔、HTTP參數(shù)解析與封裝。
[C]在頻繁進行字符串的運算(如拼接拥知、替換踏拜、刪除等),并且運行在單線程環(huán)境下低剔,建議使用StringBuilder速梗,例如SQL語句拼裝、JSON封裝等襟齿。
在HotSpot虛擬機中姻锁,對象在內(nèi)存中存儲的布局可以分為3塊區(qū)域:對象頭(Header)、實例數(shù)據(jù)(Instance Data)和對齊填充(Padding)猜欺。
HotSpot虛擬機的對象頭包括兩部分信息位隶,第一部分用于存儲對象自身的運行時數(shù)據(jù),如哈希碼(HashCode)开皿、GC分代年齡涧黄、鎖狀態(tài)標志、線程持有的鎖赋荆、偏向線程ID笋妥、偏向時間戳等,這部 分數(shù)據(jù)的長度在32位和64位的虛擬機(未開啟壓縮指針)中分別為32bit和64bit窄潭,官方稱它為"Mark Word"春宣。
另外一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針狈孔,虛擬機通過這個指針來確定這個對象是哪個類的實例信认。并不是所有的虛擬機實現(xiàn)都必須在對象數(shù)據(jù)上保留類型指針,換句話 說均抽,查找對象的元數(shù)據(jù)信息并不一定要經(jīng)過對象本身嫁赏,另外,如果對象是一個Java數(shù)組油挥,那在對象頭中還必須有一塊用于記錄數(shù)組長度的數(shù)據(jù)潦蝇,因為虛擬機可以通過普 通Java對象的元數(shù)據(jù)信息確定Java對象的大小,但是從數(shù)組的元數(shù)據(jù)中卻無法確定數(shù)組的大小深寥。
實例數(shù)據(jù)部分是對象真正存儲的有效信息攘乒,也是在程序代碼中所定義的各種類型的字段內(nèi)容。無論是從父類繼承下來的惋鹅,還是在子類中定義的则酝,都需要記錄起來。
對齊填充并不是必然存在的闰集,也沒有特別的含義沽讹,它僅僅起著占位符的作用般卑。由于HotSpot VM的自動內(nèi)存管理系統(tǒng)要求對象起始地址必須是8字節(jié)的整數(shù)倍,換句話說爽雄,就是對象的大小必須是8字節(jié)的整數(shù)倍蝠检。
JVM類加載機制分為五個部分:加載,鏈接(驗證挚瘟,準備叹谁,解析),初始化
加載:是類加載過程中的一個階段乘盖,這個階段會在內(nèi)存中生成一個代表這個類的java.lang.Class對象焰檩,作為方法區(qū)這個類的各種數(shù)據(jù)的入口。注意這里不一定非得要從一個Class文件獲取订框,這里既可以從ZIP包中讀裙尽(比如從jar包和war包中讀取)布蔗,也可以在運行時計算生成(動態(tài)代理),也可以由其它文件生成(比如將JSP文件轉換成對應的Class類)浪腐。
驗證:這一階段的主要目的是為了確保Class文件的字節(jié)流中包含的信息是否符合當前虛擬機的要求纵揍,并且不會危害虛擬機自身的安全。
準備:準備階段是正式為類變量分配內(nèi)存并設置類變量的初始值階段议街,即在方法區(qū)中分配這些變量所使用的內(nèi)存空間泽谨。注意這里所說的初始值概念,比如一個類變量定義為: public static int v = 8080;
實際上變量v在準備階段過后的初始值為0而不是8080特漩,將v賦值為8080的put static指令是程序被編譯后吧雹,存放于類構造器<client>方法之中。 但是注意如果聲明為:
public static final int v = 8080;
在編譯階段會為v生成ConstantValue屬性涂身,在準備階段虛擬機會根據(jù)ConstantValue屬性將v賦值為8080雄卷。
解析:解析階段是指虛擬機將常量池中的符號引用替換為直接引用的過程。符號引用就是class文件中的:
1. CONSTANT_Class_info
2. CONSTANT_Field_info
3. CONSTANT_Method_info 等類型的常量蛤售。
符號引用:符號引用與虛擬機實現(xiàn)的布局無關丁鹉,引用的目標并不一定要已經(jīng)加載到內(nèi)存中。各種虛擬機實現(xiàn)的內(nèi)存布局可以各不相同悴能,但是它們能接受的符號引用必須是一致的揣钦,因為符號引用的字面量形式明確定義在Java虛擬機規(guī)范的Class文件格式中。
直接引用:直接引用可以是指向目標的指針漠酿,相對偏移量或是一個能間接定位到目標的句柄冯凹。如果有了直接引用,那引用的目標必定已經(jīng)在內(nèi)存中存在炒嘲。
初始化:初始化階段是類加載最后一個階段宇姚,前面的類加載階段之后匈庭,除了在加載階段可以自定義類加載器以外,其它操作都由JVM主導空凸。到了初始階段嚎花,才開始真正執(zhí)行類中定義的Java程序代碼。
類構造器<client> :
初始化階段是執(zhí)行類構造器<client>方法的過程呀洲。<client>方法是由編譯器自動收集類中的類變量的賦值操作和靜態(tài)語句塊中的語句合并而成的紊选。虛擬機會保證子<client>方法執(zhí)行之前,父類的<client>方法已經(jīng)執(zhí)行完畢道逗,如果一個類中沒有對靜態(tài)變量賦值也沒有靜態(tài)語句塊兵罢,那么編譯器可以不為這個類生成<client>()方法。
注意以下幾種情況不會執(zhí)行類初始化:
1. 通過子類引用父類的靜態(tài)字段滓窍,只會觸發(fā)父類的初始化卖词,而不會觸發(fā)子類的初始化。
2. 定義對象數(shù)組吏夯,不會觸發(fā)該類的初始化此蜈。
3. 常量在編譯期間會存入調(diào)用類的常量池中,本質上并沒有直接引用定義常量的類噪生,不會觸發(fā)定義常量所在的類裆赵。
4. 通過類名獲取Class對象,不會觸發(fā)類的初始化跺嗽。
5. 通過Class.forName加載指定類時战授,如果指定參數(shù)initialize為false時,也不會觸發(fā)類初始化桨嫁,其實這個參數(shù)是告訴虛擬機植兰,是否要對類進行初始化。
6. 通過ClassLoader默認的loadClass方法璃吧,也不會觸發(fā)初始化動作楣导。
引起線程上下文切換的原因:
1. 當前執(zhí)行任務的時間片用完之后,系統(tǒng)CPU正常調(diào)度下一個任務肚逸;
2. 當前執(zhí)行任務碰到IO阻塞爷辙,調(diào)度器將此任務掛起,繼續(xù)下一任務朦促;
3. 多個任務搶占鎖資源膝晾,當前任務沒有搶到鎖資源,被調(diào)度器掛起务冕,繼續(xù)下一任務血当;
4. 用戶代碼掛起當前任務,讓出CPU時間;
5. 硬件中斷臊旭;
1 int和Integer
JDK1.5引入了自動裝箱與自動拆箱功能落恼,Java可根據(jù)上下文,實現(xiàn)int/Integer,double/Double,boolean/Boolean 等基本類型與相應對象之間的自動轉換离熏,為開發(fā)過程帶來極大便利佳谦。
最常用的是通過new方法構建Integer對象。但是滋戳,基于大部分數(shù)據(jù)操作都是集中在有限的钻蔑、較小的數(shù)值范圍,在JDK1.5 中新增了靜態(tài)工廠方法 valueOf奸鸯,其背后實現(xiàn)是將int值為-128 到 127 之間的Integer對象進行緩存咪笑,在調(diào)用時候直接從緩存中獲取,進而提升構建對象的性能娄涩,也就是說使用該方法后窗怒,如果兩個對象的int值相同且落在緩存值范圍內(nèi),那么這個兩個對象就是 同一個對象;當值較小且頻繁使用時蓄拣,推薦優(yōu)先使用整型池方法(時間與空間性能俱佳)扬虚。
2 注意事項
[1] 基本類型均具有取值范圍,在大數(shù)*大數(shù)的時候球恤,有可能會出現(xiàn)越界的情況孔轴。
[2] 基本類型轉換時,使用聲明的方式碎捺。例:long result= 1234567890 * 24 * 365;結果值一定不會是你所期望的那個值,因為1234567890 * 24已經(jīng)超過了int的范圍贷洲,如果修改 為:long result= 1234567890L * 24 * 365;就正常了收厨。
[3] 慎用基本類型處理貨幣存儲。如采用double常會帶來差距优构,常采用BigDecimal诵叁、整型(如果要精確表示分,可將值擴大100倍轉化為整型)解決該問題钦椭。
[4] 優(yōu)先使用基本類型拧额。原則上,建議避免無意中的裝箱彪腔、拆箱行為侥锦,尤其是在性能敏感的場合,
[5] 如果有線程安全的計算需要德挣,建議考慮使用類型AtomicInteger恭垦、AtomicLong 這樣的線程安全類。部分比較寬的基本數(shù)據(jù)類型,比如 foat番挺、double唠帝,甚至不能保證更新操作的原子性, 可能出現(xiàn)程序讀取到只更新了一半數(shù)據(jù)位的數(shù)值玄柏。
CyclicBarrier和CountDownLatch的區(qū)別是:
(01) CountDownLatch的作用是允許1或N個線程等待其他線程完成執(zhí)行襟衰;而CyclicBarrier則是允許N個線程相互等待。
(02) CountDownLatch的計數(shù)器無法被重置粪摘;CyclicBarrier的計數(shù)器可以被重置后使用瀑晒,因此它被稱為是循環(huán)的barrier。
ReentrantLock是一個可重入的互斥鎖赶熟,又被稱為“獨占鎖”瑰妄。
顧名思義,ReentrantLock鎖在同一個時間點只能被一個線程鎖持有映砖;而可重入的意思是间坐,ReentrantLock鎖,可以被單個線程多次獲取邑退。
ReentrantLock分為“公平鎖”和“非公平鎖”竹宋。它們的區(qū)別體現(xiàn)在獲取鎖的機制上是否公平〉丶迹“鎖”是為了保護競爭資源蜈七,防止多個線程同時操作線程而出錯,ReentrantLock在同一個時間點只能被一個線程獲取(當某線程獲取到“鎖”時莫矗,其它線程就必須等待)飒硅;ReentraantLock是通過一個FIFO的等待隊列來管理獲取該鎖所有線程的。在“公平鎖”的機制下作谚,線程依次排隊獲取鎖三娩;而“非公平鎖”在鎖是可獲取狀態(tài)時,不管自己是不是在隊列的開頭都會獲取鎖妹懒。默認是“非公平鎖”雀监。
AQS鎖的類別 -- 分為“獨占鎖”和“共享鎖”兩種。
? ? (01) 獨占鎖 -- 鎖在一個時間點只能被一個線程鎖占有眨唬。根據(jù)鎖的獲取機制会前,它又劃分為“公平鎖”和“非公平鎖”。公平鎖匾竿,是按照通過CLH等待線程按照先來先得的規(guī)則瓦宜,公平的獲取鎖;而非公平鎖岭妖,則當線程要獲取鎖時歉提,它會無視CLH等待隊列而直接獲取鎖笛坦。獨占鎖的典型實例子是ReentrantLock,此外苔巨,ReentrantReadWriteLock.WriteLock也是獨占鎖版扩。
? ? (02) 共享鎖 -- 能被多個線程同時擁有,能被共享的鎖侄泽。JUC包中的ReentrantReadWriteLock.ReadLock礁芦,CyclicBarrier, CountDownLatch和Semaphore都是共享鎖悼尾。這些鎖的用途和原理柿扣,在以后的章節(jié)再詳細介紹。
CLH隊列 -- Craig, Landin, and Hagersten lock queue
? ? CLH隊列是AQS中“等待鎖”的線程隊列闺魏。在多線程中未状,為了保護競爭資源不被多個線程同時操作而起來錯誤,我們常常需要通過鎖來保護這些資源析桥。在獨占鎖中司草,競爭資源在一個時間點只能被一個線程鎖訪問;而其它線程則需要等待泡仗。CLH就是管理這些“等待鎖”的線程的隊列埋虹。
? ? CLH是一個非阻塞的 FIFO 隊列。也就是說往里面插入或移除一個節(jié)點的時候娩怎,在并發(fā)條件下不會阻塞搔课,而是通過自旋鎖和 CAS 保證節(jié)點插入和移除的原子性。
(01) 先是通過tryAcquire()嘗試獲取鎖截亦。獲取成功的話爬泥,直接返回;嘗試失敗的話崩瓤,再通過acquireQueued()獲取鎖急灭。
(02) 嘗試失敗的情況下,會先通過addWaiter()來將“當前線程”加入到"CLH隊列"末尾谷遂;然后調(diào)用acquireQueued(),在CLH隊列中排序等待獲取鎖卖鲤,在此過程中肾扰,線程處于休眠狀態(tài)。直到獲取鎖了才返回蛋逾。 如果在休眠等待過程中被中斷過集晚,則調(diào)用selfInterrupt()來自己產(chǎn)生一個中斷。
“釋放鎖”的過程相對“獲取鎖”的過程比較簡單区匣。釋放鎖時偷拔,主要進行的操作,是更新當前線程對應的鎖的狀態(tài)。如果當前線程對鎖已經(jīng)徹底釋放莲绰,則設置“鎖”的持有線程為null欺旧,設置當前線程的狀態(tài)為空,然后喚醒后繼線程蛤签。
公平鎖和非公平鎖的區(qū)別辞友,是在獲取鎖的機制上的區(qū)別。表現(xiàn)在震肮,在嘗試獲取鎖時 —— 公平鎖称龙,只有在當前線程是CLH等待隊列的表頭時,才獲取鎖戳晌;而非公平鎖鲫尊,只要當前鎖處于空閑狀態(tài),則直接獲取鎖沦偎,而不管CLH等待隊列中的順序疫向。
只有當非公平鎖嘗試獲取鎖失敗的時候,它才會像公平鎖一樣扛施,進入CLH等待隊列排序等待鸿捧。
JUC中的共享鎖有CountDownLatch, CyclicBarrier, Semaphore, ReentrantReadWriteLock等
(01) ReentrantReadWriteLock實現(xiàn)了ReadWriteLock接口。ReadWriteLock是一個讀寫鎖的接口疙渣,提供了"獲取讀鎖的readLock()函數(shù)" 和 "獲取寫鎖的writeLock()函數(shù)"匙奴。
(02) ReentrantReadWriteLock中包含:sync對象,讀鎖readerLock和寫鎖writerLock妄荔。讀鎖ReadLock和寫鎖WriteLock都實現(xiàn)了Lock接口泼菌。讀鎖ReadLock和寫鎖WriteLock中也都分別包含了"Sync對象",它們的Sync對象和ReentrantReadWriteLock的Sync對象 是一樣的啦租,就是通過sync哗伯,讀鎖和寫鎖實現(xiàn)了對同一個對象的訪問。
(03) 和"ReentrantLock"一樣篷角,sync是Sync類型焊刹;而且,Sync也是一個繼承于AQS的抽象類恳蹲。Sync也包括"公平鎖"FairSync和"非公平鎖"NonfairSync虐块。sync對象是"FairSync"和"NonfairSync"中的一個,默認是"NonfairSync"嘉蕾。
Semaphore是一個計數(shù)信號量贺奠,它的本質是一個"共享鎖"。
信號量維護了一個信號量許可集错忱。線程可以通過調(diào)用acquire()來獲取信號量的許可儡率;當信號量中有可用的許可時挂据,線程能獲取該許可;否則線程必須等待儿普,直到有可用的許可為止崎逃。 線程可以通過release()來釋放它所持有的信號量許可。
JMM定義了線程和主內(nèi)存之間的抽象關系:線程之間的共享變量存儲在主內(nèi)存(Main Memory)中箕肃,每個線程都有一個私有的本地內(nèi)存(Local Memory)婚脱,本地內(nèi)存中存儲了該線程以讀/寫共享變量的副本。本地內(nèi)存是JMM的一個抽象概念勺像,并不真實存在障贸。它涵蓋了緩存、寫緩沖區(qū)、寄存器以及其他的硬件和編譯器優(yōu)化。
JMM特征: 原子性(synchronized)觉吭、有序性(volatile假消、synchronized)淹真、可見性(volatile、synchronized、final)
as-if-serial
不管如何重排序,都必須保證代碼在單線程下的運行正確篷牌。
重排序(volatile、synchronized踏幻、final枷颊、Lock)
定義:重排序是指“編譯器和處理器”為了提高性能,而在程序執(zhí)行時會對程序進行的重排序该面。
說明:重排序分為——“編譯器”和“處理器”兩個方面夭苗,而“處理器”重排序又包括“指令級重排序”和“內(nèi)存的重排序”。
關于重排序隔缀,我們需要理解它的思想:為了提高程序的并發(fā)度题造,從而提高性能!但是對于多線程程序猾瘸,重排序可能會導致程序執(zhí)行的結果不是我們需要的結果界赔!因此,就需要我們通過“volatile牵触,synchronize淮悼,鎖等方式”作出正確的實現(xiàn)同步。
內(nèi)存屏障
定義:包括LoadLoad, LoadStore, StoreLoad, StoreStore共4種內(nèi)存屏障荒吏。內(nèi)存屏障是與相應的內(nèi)存重排序相對應的。
作用:通過內(nèi)存屏障可以禁止特定類型處理器的重排序渊鞋,從而讓程序按我們預想的流程去執(zhí)行绰更。
happens-before
定義:JDK5(JSR-133)提供的概念瞧挤,用于描述多線程操作之間的內(nèi)存可見性。如果一個操作執(zhí)行的結果需要對另一個操作可見儡湾,那么這兩個操作之間必須存在happens-before關系特恬。
作用:描述多線程操作之間的內(nèi)存可見性。
[程序順序規(guī)則]:一個線程中的每個操作徐钠,happens- before 于該線程中的任意后續(xù)操作癌刽。
[監(jiān)視器鎖規(guī)則]:對一個監(jiān)視器鎖的解鎖,happens- before 于隨后對這個監(jiān)視器鎖的加鎖尝丐。
[volatile變量規(guī)則]:對一個volatile域的寫显拜,happens- before 于任意后續(xù)對這個volatile域的讀。
[傳遞性]:如果A happens- before B爹袁,且B happens- before C远荠,那么A happens- before C。
注意失息,兩個操作之間具有 happens-before 關系譬淳,并不意味著前一個操作必須要在后一個操作之前執(zhí)行!happens-before 僅僅要求前一個操作(執(zhí)行的結果)對后一個操作可見盹兢,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)邻梆。
分配內(nèi)存: new 出來的對象不一定都分配在堆上,其中有 棧上分配(逃逸分析)和 tlab (線程本地分配緩存區(qū))绎秒。
棧上分配:
? 逃逸分析:對象的作用域都不會逃逸出方法外浦妄,也就是說該對象的生命周期會隨著方法的調(diào)用開始而開始,方法的調(diào)用結束而結束替裆。(分配棧上)
? 標量替換:允許將對象打散分配在棧上校辩,比如若一個對象擁有兩個字段,會將這兩個字段視作局部變量進行分配辆童。
tlab: 線程私有的 (eden區(qū)tlab分配)
JVM會先進行棧上分配宜咒,不符合會進行tlab分配,如果tlab分配不成功在嘗試在eden區(qū)分配把鉴,如果對象滿足了直接進入老年代的條件故黑,直接分配老年代。
分配內(nèi)存的兩種方法: 指針碰撞和空閑列表
指針碰撞:假設JVM虛擬機上庭砍,堆內(nèi)存都是規(guī)整的场晶。堆內(nèi)存被一個指針一分為二。指針的左邊都被塞滿了對象怠缸,指針的右變是未使用的區(qū)域诗轻。每一次有新的對象創(chuàng)建,指針就會向右移動一個對象size的距離揭北。這就被稱為指針碰撞扳炬。
空閑列表:如果Java堆中的內(nèi)存并不是規(guī)整的吏颖,已使用的內(nèi)存和空閑的內(nèi)存相互交錯,那就沒有辦法簡單地進行指針碰撞了恨樟,虛擬機就必須維護一個列表半醉,記錄上哪些內(nèi)存塊是可用的,在分配的時候從列表中找到一塊足夠大的空間劃分給對象實例劝术,并更新列表上的記錄缩多,這種分配方式稱為“空閑列表”(Free List)
JVM內(nèi)存區(qū)域:
線程私有: 程序計數(shù)器、棧养晋、本地方法棧
棧幀:局步變量表衬吆、操作數(shù)棧、動態(tài)鏈接匙握、方法出口
線程共享: 堆咆槽、metaspace(元空間)、直接內(nèi)存(堆外內(nèi)存)
metaspace:類元數(shù)據(jù)
堆: 新生代(eden)圈纺、老年代秦忿、運行時常量池(符號引用(類完全限定名、字段名稱描述符蛾娶、方法名稱描述符)灯谣、字面量:字符串、final常量蛔琅、基本數(shù)據(jù)類型胎许、其他)
如何確定對象是不是垃圾: 引用計數(shù),可達性分析
引用計數(shù):個對象如果沒有任何與之關聯(lián)的引用罗售,即他們的引用計數(shù)都不為0辜窑,則說明對象不太可能再被用到,那么這個對象就是可回收對象寨躁。
可達性分析:如果在“GC roots”和一個對象之間沒有可達路徑穆碎,則稱該對象是不可達的,不可達對象變?yōu)榭苫厥諏ο笾辽僖?jīng)過兩次標記過程职恳。兩次標記后仍然是可回收對象所禀,則將面臨回收。
復制算法:新生代放钦,每次垃圾收集都能發(fā)現(xiàn)大批對象已死, 只有少量存活. 因此選用復制算法, 只需要付出少量存活對象的復制成本就可以完成收集.
標記復制算法:老年代色徘,因為對象存活率高、沒有額外空間對它進行分配擔保, 就必須采用“標記—清理”或“標記—整理”算法來進行回收, 不必進行內(nèi)存復制, 且直接騰出空閑內(nèi)存.
CMS收集器(多線程標記清除算法):
CMS收集器是一種年老代垃圾收集器操禀,其最主要目標是獲取最短垃圾回收停頓時間褂策,和其他年老代使用標記-整理算法不同,它使用多線程的標記-清除算法。 最短的垃圾收集停頓時間可以為交互比較高的程序提高用戶體驗斤寂。? CMS工作機制相比其他的垃圾收集器來說更復雜蔑水,整個過程分為以下4個階段:
1。初始標記:只是標記一下GC Roots能直接關聯(lián)的對象扬蕊,速度很快,仍然需要暫停所有的工作線程丹擎。
2尾抑。并發(fā)標記 進行GC Roots跟蹤的過程,和用戶線程一起工作蒂培,不需要暫停工作線程再愈。
3。重新標記 為了修正在并發(fā)標記期間护戳,因用戶程序繼續(xù)運行而導致標記產(chǎn)生變動的那一部分對象的標記記錄翎冲,仍然需要暫停所有的工作線程。
4媳荒。并發(fā)清除 清除GC Roots不可達對象抗悍,和用戶線程一起工作,不需要暫停工作線程钳枕。由于耗時最長的并發(fā)標記和并發(fā)清除過程中缴渊,垃圾收集線程可以和用戶現(xiàn)在一起并發(fā)工作,所以總體上來看CMS收集器的內(nèi)存回收和用戶線程是一起并發(fā)地執(zhí)行鱼炒。
G1收集器 Garbage first垃圾收集器是目前垃圾收集器理論發(fā)展的最前沿成果衔沼,相比與CMS收集器,G1收集器兩個最突出的改進是: 1. 基于標記-整理算法昔瞧,不產(chǎn)生內(nèi)存碎片指蚁。 2. 可以非常精確控制停頓時間,在不犧牲吞吐量前提下自晰,實現(xiàn)低停頓垃圾回收凝化。 G1收集器避免全區(qū)域垃圾收集,它把堆內(nèi)存劃分為大小固定的幾個獨立區(qū)域缀磕,并且跟蹤這些區(qū)域的垃圾收集進度缘圈,同時在后臺維護一個優(yōu)先級列表,每次根據(jù)所允許的收集時間袜蚕,優(yōu)先回收垃圾最多的區(qū)域糟把。區(qū)域劃分和優(yōu)先級區(qū)域回收機制,確保G1收集器可以在有限時間獲得最高的垃圾收集效率
volatile
作用: 如果一個變量是volatile類型牲剃,則對該變量的讀寫就將具有原子性遣疯。如果是多個volatile操作或類似于volatile++這種復合操作,這些操作整體上不具有原子性凿傅。volatile變量自身具有下列特性:
[可見性]:對一個volatile變量的讀缠犀,總是能看到(任意線程)對這個volatile變量最后的寫入数苫。
[原子性]:對任意單個volatile變量的讀/寫具有原子性,但類似于volatile++這種復合操作不具有原子性辨液。
volatile的內(nèi)存語義:
volatile寫:當寫一個volatile變量時虐急,JMM會把該線程對應的本地內(nèi)存中的共享變量刷新到主內(nèi)存。
volatile讀:當讀一個volatile變量時滔迈,JMM會把該線程對應的本地內(nèi)存置為無效止吁。線程接下來將從主內(nèi)存中讀取共享變量。
volatile和 synchronize對比:
在功能上燎悍,監(jiān)視器鎖比volatile更強大敬惦;在可伸縮性和執(zhí)行性能上,volatile更有優(yōu)勢谈山。
volatile僅僅保證對單個volatile變量的讀/寫具有原子性俄删;而synchronize鎖的互斥執(zhí)行的特性可以確保對整個臨界區(qū)代碼的執(zhí)行具有原子性。
同步鎖synchronized:
java的內(nèi)置鎖:每個java對象都可以用做一個實現(xiàn)同步的鎖奏路,這些鎖成為內(nèi)置鎖畴椰。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖鸽粉。獲得內(nèi)置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法迅矛。
java內(nèi)置鎖是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖潜叛,當線程A嘗試去獲得線程B持有的內(nèi)置鎖時秽褒,線程A必須等待或者阻塞,知道線程B釋放這個鎖威兜,如果B線程不釋放這個鎖销斟,那么A線程將永遠等待下去。(目的:只有一個線程可執(zhí)行)
Lock和synchronized區(qū)別:
1.首先synchronized是java內(nèi)置關鍵字椒舵,在jvm層面蚂踊,Lock是個java類;
2.synchronized無法判斷是否獲取鎖的狀態(tài)笔宿,Lock可以判斷是否獲取到鎖犁钟;
3.synchronized會自動釋放鎖(a?線程執(zhí)行完同步代碼會釋放鎖 ;b 線程執(zhí)行過程中發(fā)生異常會釋放鎖)泼橘,Lock需在finally中手工釋放鎖(unlock()方法釋放鎖)涝动,否則容易造成線程死鎖;
4.用synchronized關鍵字的兩個線程1和線程2炬灭,如果當前線程1獲得鎖醋粟,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去米愿,而Lock鎖就不一定會等待下去厦凤,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了育苟;
5.synchronized的鎖可重入较鼓、不可中斷、非公平违柏,而Lock鎖可重入笨腥、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題勇垛,synchronized鎖適合代碼少量的同步問題。
7.最重要的是Lock是一個接口士鸥,而synchronized是一個關鍵字闲孤,synchronized放棄鎖只有兩種情況:①線程執(zhí)行完了同步代碼塊的內(nèi)容②發(fā)生異常;而lock不同烤礁,它可以設定超時時間讼积,也就是說他可以在獲取鎖時便設定超時時間,如果在你設定的時間內(nèi)它還沒有獲取到鎖脚仔,那么它會放棄獲取鎖然后響應放棄操作勤众。
JMM如何實現(xiàn)鎖
公平鎖:
公平鎖是通過“volatile”實現(xiàn)同步的。公平鎖在釋放鎖的最后寫volatile變量state鲤脏;在獲取鎖時首先讀這個volatile變量们颜。根據(jù)volatile的happens-before規(guī)則,釋放鎖的線程在寫volatile變量之前可見的共享變量猎醇,在獲取鎖的線程讀取同一個volatile變量后將立即變的對獲取鎖的線程可見窥突。
非公平鎖:
通過CAS實現(xiàn)的,CAS就是compare and swap硫嘶。CAS實際上調(diào)用的JNI函數(shù)阻问,也就是CAS依賴于本地實現(xiàn)。以Intel來說沦疾,對于CAS的JNI實現(xiàn)函數(shù)称近,它保證:(01)禁止該CAS之前和之后的讀和寫指令重排序。(02)把寫緩沖區(qū)中的所有數(shù)據(jù)刷新到內(nèi)存中哮塞。
JMM通過“內(nèi)存屏障”實現(xiàn)final,在final域的寫之后刨秆,構造函數(shù)return之前,插入一個StoreStore障屏忆畅。在讀final域的操作前面插入一個LoadLoad屏障坛善。
重排序分三種類型:
編譯器優(yōu)化的重排序。編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序眠屎。
指令級并行的重排序√藿唬現(xiàn)代處理器采用了指令級并行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執(zhí)行改衩。如果不存在數(shù)據(jù)依賴性岖常,處理器可以改變語句對應機器指令的執(zhí)行順序。
內(nèi)存系統(tǒng)的重排序葫督。由于處理器使用緩存和讀 / 寫緩沖區(qū)竭鞍,這使得加載和存儲操作看上去可能是在亂序執(zhí)行。
spring
Eureka與Consul對比:
Consul強一致性(C)帶來的是:(CA)
服務注冊相比Eureka會稍慢一些橄镜。因為Consul的raft協(xié)議要求必須過半數(shù)的節(jié)點都寫入成功才認為注冊成功
Leader掛掉時偎快,重新選舉期間整個consul不可用。保證了強一致性但犧牲了可用性洽胶。
Eureka保證高可用(A)和最終一致性:(AP)
服務注冊相對要快晒夹,因為不需要等注冊信息replicate到其他節(jié)點,也不保證注冊信息是否replicate成功
當數(shù)據(jù)出現(xiàn)不一致時姊氓,雖然A, B上的注冊信息不完全相同丐怯,但每個Eureka節(jié)點依然能夠正常對外提供服務,這會出現(xiàn)查詢服務信息時如果請求A查不到翔横,但請求B就能查到读跷。如此保證了可用性但犧牲了一致性。
其他方面禾唁,eureka就是個servlet程序效览,跑在servlet容器中; Consul則是go編寫而成。
Zookeeper角色:
Zookeeper集群是一個基于主從復制的高可用集群荡短,每個服務器承擔如下三種角色中的一種
Leader: 1. 一個Zookeeper集群同一時間只會有一個實際工作的Leader钦铺,它會發(fā)起并維護與各Follwer及Observer間的心跳。
1. 所有的寫操作必須要通過Leader完成再由Leader將寫操作廣播給其它服務器肢预。只要有超過半數(shù)節(jié)點(不包括observeer節(jié)點)寫入成功矛洞,該寫請求就會被提交(類 2PC 協(xié)議)。
Follower:? 1. 一個Zookeeper集群可能同時存在多個Follower烫映,它會響應Leader的心跳沼本,
2. Follower可直接處理并返回客戶端的讀請求,同時會將寫請求轉發(fā)給Leader處理锭沟,
3. 并且負責在Leader處理寫請求時對請求進行投票抽兆。
Observer:? 角色與Follower類似,但是無投票權族淮。Zookeeper需保證高可用和強一致性辫红,為了支持更多的客戶端凭涂,需要增加更多Server;Server增多贴妻,投票階段延遲增大切油,影響性能;引入Observer名惩,Observer不參與投票澎胡; Observers接受客戶端的連接,并將寫請求轉發(fā)給leader節(jié)點娩鹉; 加入更多Observer節(jié)點攻谁,提高伸縮性,同時不影響吞吐率弯予。
ZAB:
1. Leader election(選舉階段):節(jié)點在一開始都處于選舉階段戚宦,只要有一個節(jié)點得到超半數(shù)節(jié)點的票數(shù),它就可以當選準 leader锈嫩。只有到達 廣播階段(broadcast) 準 leader 才會成為真正的 leader受楼。這一階段的目的是就是為了選出一個準 leader,然后進入下一個階段祠挫。
2.? Discovery(發(fā)現(xiàn)階段):在這個階段,followers 跟準 leader 進行通信悼沿,同步 followers 最近接收的事務提議等舔。這個一階段的主要目的是發(fā)現(xiàn)當前大多數(shù)節(jié)點接收的最新提議,并且準 leader 生成新的 epoch糟趾,讓 followers 接受慌植,更新它們的 accepted Epoch 一個 follower 只會連接一個 leader,如果有一個節(jié)點 f 認為另一個 follower p 是 leader义郑,f 在嘗試連接 p 時會被拒絕蝶柿,f 被拒絕之后,就會進入重新選舉階段非驮。
3.? Synchronization(同步階段):同步階段主要是利用 leader 前一階段獲得的最新提議歷史交汤,同步集群中所有的副本。只有當 大多數(shù)節(jié)點都同步完成劫笙,準 leader 才會成為真正的 leader芙扎。follower 只會接收 zxid 比自己的 lastZxid 大的提議。 Broadcast(廣播階段-leader消息廣播)
4.? Broadcast(廣播階段):到了這個階段填大,Zookeeper 集群才能正式對外提供事務服務戒洼,并且 leader 可以進行消息廣播。同時如果有新的節(jié)點加入允华,還需要對新節(jié)點進行同步圈浇。 ZAB 提交事務并不像 2PC 一樣需要全部 follower 都 ACK寥掐,只需要得到超過半數(shù)的節(jié)點的 ACK 就可以了。
RabbitMQ 最初起源于金融系統(tǒng)磷蜀,用于在分布式系統(tǒng)中存儲轉發(fā)消息召耘,在易用性、擴展性蠕搜、高可用性等方面表現(xiàn)不俗怎茫。具體特點包括:
1. 可靠性(Reliability):RabbitMQ 使用一些機制來保證可靠性,如持久化妓灌、傳輸確認轨蛤、發(fā)布確認。
2. 靈活的路由(Flexible Routing):在消息進入隊列之前虫埂,通過 Exchange 來路由消息的祥山。對于典型的路由功能,RabbitMQ 已經(jīng)提供了一些內(nèi)置的 Exchange 來實現(xiàn)掉伏。針對更復雜的路由功能缝呕,可以將多個 Exchange 綁定在一起,也通過插件機制實現(xiàn)自己的 Exchange 斧散。
3. 消息集群(Clustering):多個 RabbitMQ 服務器可以組成一個集群供常,形成一個邏輯 Broker 。
4. 高可用(Highly Available Queues):隊列可以在集群中的機器上進行鏡像鸡捐,使得在部分節(jié)點出問題的情況下隊列仍然可用栈暇。
5. 多種協(xié)議(Multi-protocol):RabbitMQ 支持多種消息隊列協(xié)議,比如 STOMP箍镜、MQTT 等等源祈。
6. 多語言客戶端(Many Clients):RabbitMQ 幾乎支持所有常用語言,比如 Java色迂、.NET香缺、Ruby 等等。
7. 管理界面(Management UI):RabbitMQ 提供了一個易用的用戶界面歇僧,使得用戶可以監(jiān)控和管理消息 Broker 的許多方面图张。
8. 跟蹤機制(Tracing):如果消息異常,RabbitMQ 提供了消息跟蹤機制诈悍,使用者可以找出發(fā)生了什么埂淮。
9. 插件機制(Plugin System):RabbitMQ 提供了許多插件,來從多方面進行擴展写隶,也可以編寫自己的插件倔撞。
1. Direct:消息中的路由鍵(routing key)如果和 Binding 中的 binding key 一致, 交換器就將消息發(fā)到對應的隊列中慕趴。它是完全匹配痪蝇、單播的模式鄙陡。
2. Fanout(廣播分發(fā)):每個發(fā)到 fanout 類型交換器的消息都會分到所有綁定的隊列上去。很像子網(wǎng)廣播躏啰,每臺子網(wǎng)內(nèi)的主機都獲得了一份復制的消息趁矾。fanout 類型轉發(fā)消息是最快的。
3. topic 交換器:topic 交換器通過模式匹配分配消息的路由鍵屬性给僵,將路由鍵和某個模式進行匹配毫捣,此時隊列需要綁定到一個模式上。它將路由鍵和綁定鍵的字符串切分成單詞帝际,這些單詞之間用點隔開蔓同。它同樣也會識別兩個通配符:符號“#”和符號“”。#匹配0個或多個單詞蹲诀,匹配不多不少一個單詞斑粱。
docker : namespace : 環(huán)境隔離(進程) , cgroup:資源隔離(cpu脯爪、內(nèi)存)
k8S:
master:master節(jié)點是k8s集群的控制節(jié)點则北,負責整個集群的管理與控制。
kube-apiserver:集群控制的入口痕慢,提供http服務尚揣。
kube-controller-manager:集群中所有資源對象的自動化控制中心。
kube-scheduler:負責pod的調(diào)度
node:NODE節(jié)點是k8s集群中的工作節(jié)點掖举,Node上的工作負載由master節(jié)點分配快骗,工作負載主要是運行容器應用。
kubelet:負責Pod的創(chuàng)建拇泛、啟動滨巴、監(jiān)控思灌、重啟俺叭、銷毀等工作,同時與master節(jié)點協(xié)作泰偿,實現(xiàn)集群管理的基本功能熄守。
kube-proxy:實現(xiàn)k8s service的通信和負載均衡。
Pod:k8s最基本的部署調(diào)度單元耗跛,每個pod可以由一個或多個業(yè)務容器和一個根容器(pause)組成裕照,一個pod表示一個應用實例。
Deployment:表示部署调塌,內(nèi)部用replicaset實現(xiàn)晋南,可以用來生成相應的replicaset完成pod副本的創(chuàng)建。
service:k8S最總要的資源對象羔砾,k8S重的service對象可以對應微服務中的服務负间,service定義了服務的訪問入口偶妖,服務的調(diào)用者通過這個地址訪問service后段的pod副本實例。service通過label selector同pod副本建立關系政溃,deployment保證pod副本的數(shù)量趾访,也是保證服務的伸縮性。
Pod創(chuàng)建流程:? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
●用戶通過REST API創(chuàng)建一個Pod
●apiserver將其寫入etcd
●scheduluer 檢測到未綁定Node的Pod,開始調(diào)度并更新Pod的Node綁定
●kubelet檢測到有新的Pod調(diào)度過來董虱,通過container runtime運行該Pod
●kubelet 通過container runtime取到Pod狀態(tài)扼鞋,并更新到apiserver中
原子性(Atomicity)
原子性是指事務是一個不可分割的工作單位,事務中的操作要么都發(fā)生愤诱,要么都不發(fā)生云头。
一致性(Consistency)
事務前后數(shù)據(jù)的完整性必須保持一致。
隔離性(Isolation)
事務的隔離性是多個用戶并發(fā)訪問數(shù)據(jù)庫時转锈,數(shù)據(jù)庫為每一個用戶開啟的事務盘寡,不能被其他事務的操作數(shù)據(jù)所干擾,多個并發(fā)事務之間要相互隔離撮慨。
持久性(Durability)
持久性是指一個事務一旦被提交竿痰,它對數(shù)據(jù)庫中數(shù)據(jù)的改變就是永久性的,接下來即使數(shù)據(jù)庫發(fā)生故障也不應該對其有任何影響
事務的隔離級別分為:未提交讀(read uncommitted)砌溺、已提交讀(read committed)影涉、可重復讀(repeatable read)、串行化(serializable)规伐。