互聯(lián)網(wǎng)的快速發(fā)展,Java開(kāi)發(fā)的過(guò)程或多或少會(huì)需要進(jìn)行并發(fā)編程审洞,也會(huì)遇到一些并發(fā)編程帶來(lái)的各種bug。下面從并發(fā)編程的理論待讳、并發(fā)工具類(lèi)芒澜、并發(fā)設(shè)計(jì)模式、并發(fā)模型案例创淡,記錄一下自己的學(xué)習(xí)歷程痴晦。
1.并發(fā)編程理論
1.1 可見(jiàn)性、原子性辩昆、有序性
并發(fā)編程的來(lái)源于緩存導(dǎo)致的可見(jiàn)性問(wèn)題阅酪,線(xiàn)程切換帶來(lái)的原子性問(wèn)題,編譯優(yōu)化帶來(lái)的有序性問(wèn)題汁针,也就是并發(fā)編程需要遵循的三個(gè)原則术辐。
可見(jiàn)性:一個(gè)線(xiàn)程對(duì)共享變量的修改,另外一個(gè)線(xiàn)程能夠立刻看到
原子性:一個(gè)或者多個(gè)操作在 CPU 執(zhí)行的過(guò)程中不被中斷的特性
有序性:程序按照代碼的先后順序執(zhí)行
1.2 Java內(nèi)存模型
Java語(yǔ)言規(guī)范引入了Java內(nèi)存模型施无,通過(guò)定義多項(xiàng)規(guī)則對(duì)編譯器和處理器進(jìn)行限制辉词,主要是針對(duì)可見(jiàn)性和有序性。主要是通過(guò)volatile猾骡、synchronized 和 final 三個(gè)關(guān)鍵字瑞躺,以及Happens-Before 規(guī)則。
(1)鎖兴想,鎖操作是具備happens-before關(guān)系的幢哨,解鎖操作happens-before之后對(duì)同一把鎖的加鎖操作。實(shí)際上嫂便,在解鎖的時(shí)候捞镰,JVM需要強(qiáng)制刷新緩存,使得當(dāng)前線(xiàn)程所修改的內(nèi)存對(duì)其他線(xiàn)程可見(jiàn)毙替。
(2)volatile字段岸售,volatile字段可以看成是一種不保證原子性的同步但保證可見(jiàn)性的特性,其性能往往是優(yōu)于鎖操作的厂画。但是凸丸,頻繁地訪(fǎng)問(wèn) volatile字段也會(huì)出現(xiàn)因?yàn)椴粩嗟貜?qiáng)制刷新緩存而影響程序的性能的問(wèn)題。
(3)final修飾符袱院,final修飾的實(shí)例字段則是涉及到新建對(duì)象的發(fā)布問(wèn)題屎慢。當(dāng)一個(gè)對(duì)象包含final修飾的實(shí)例字段時(shí)瞭稼,其他線(xiàn)程能夠看到已經(jīng)初始化的final實(shí)例字段,這是安全的腻惠。
Happpens-Before規(guī)則:
(1)程序次序規(guī)則:在一個(gè)線(xiàn)程內(nèi)弛姜,按照程序代碼順序,書(shū)寫(xiě)在前面的操作先行發(fā)生于書(shū)寫(xiě)在后面的操作妖枚。準(zhǔn)確地說(shuō),應(yīng)該是控制流順序而不是程序代碼順序苍在,因?yàn)橐紤]分支绝页、循環(huán)等結(jié)構(gòu)。
(2)管程鎖定規(guī)則:一個(gè)unlock操作先行發(fā)生于后面對(duì)同一個(gè)鎖的lock操作寂恬。這里必須強(qiáng)調(diào)的是同一個(gè)鎖续誉,而"后面"是指時(shí)間上的先后順序。
(3)volatile變量規(guī)則:對(duì)一個(gè)volatile變量的寫(xiě)操作先行發(fā)生于后面對(duì)這個(gè)變量的讀操作初肉,這里的"后面"同樣是指時(shí)間上的先后順序酷鸦。
(4)線(xiàn)程啟動(dòng)規(guī)則:Thread對(duì)象的start()方法先行發(fā)生于此線(xiàn)程的每一個(gè)動(dòng)作。
(5)線(xiàn)程終止規(guī)則:線(xiàn)程中的所有操作都先行發(fā)生于對(duì)此線(xiàn)程的終止檢測(cè)牙咏,我們可以通過(guò)Thread.join()方法結(jié)束臼隔、Thread.isAlive()的返回值等手段檢測(cè)到線(xiàn)程已經(jīng)終止執(zhí)行。
(6)線(xiàn)程中斷規(guī)則:對(duì)線(xiàn)程interrupt()方法的調(diào)用先行發(fā)生于被中斷線(xiàn)程的代碼檢測(cè)到中斷事件的發(fā)生妄壶,可以通過(guò)Thread.interrupted()方法檢測(cè)到是否有中斷發(fā)生摔握。
(7)對(duì)象終結(jié)規(guī)則:一個(gè)對(duì)象的初始化完成(構(gòu)造函數(shù)執(zhí)行結(jié)束)先行發(fā)生于它的finalize()方法的開(kāi)始。
1.3 互斥鎖
互斥:同一個(gè)時(shí)刻只有一個(gè)線(xiàn)程在運(yùn)行丁寄。鎖是一種通用的技術(shù)方案,Java 語(yǔ)言提供的 synchronized 關(guān)鍵字,就是鎖的一種實(shí)現(xiàn)坚弱。synchronized 關(guān)鍵字可以用來(lái)修飾方法酒奶,也可以用來(lái)修飾代碼塊。
class X {
? // 修飾非靜態(tài)方法
? synchronized void foo() {
? ? // 臨界區(qū)
? }
? // 修飾靜態(tài)方法
? synchronized static void bar() {
? ? // 臨界區(qū)
? }
? // 修飾代碼塊
? Object obj = new Object()屑埋;
? void baz() {
? ? synchronized(obj) {
? ? ? // 臨界區(qū)
? ? }
? }
}?
當(dāng)修飾靜態(tài)方法的時(shí)候豪筝,鎖定的是當(dāng)前類(lèi)的 Class 對(duì)象;當(dāng)修飾非靜態(tài)方法的時(shí)候雀彼,鎖定的是當(dāng)前實(shí)例對(duì)象 this壤蚜。鎖的本質(zhì)是在鎖定對(duì)象的頭部字段寫(xiě)入鎖定狀態(tài)和線(xiàn)程信息,所以鎖定的對(duì)象需要是一個(gè)不變的對(duì)象徊哑。
當(dāng)用一把鎖鎖住多個(gè)資源袜刷,性能太差,會(huì)造成幾個(gè)操作都是串行莺丑。所以可以用多把鎖分別鎖不同的資源著蟹,不同的操作可以并行操作墩蔓。用不同的鎖對(duì)受保護(hù)資源進(jìn)行精細(xì)化管理,能夠提升性能萧豆。這種鎖還有個(gè)名字奸披,叫細(xì)粒度鎖。當(dāng)然這樣又會(huì)造成死鎖的情況出現(xiàn)涮雷。要避免死鎖就需要分析死鎖發(fā)生的條件阵面,有個(gè)叫 Coffman 的牛人早就總結(jié)過(guò)了,只有以下這四個(gè)條件都發(fā)生時(shí)才會(huì)出現(xiàn)死鎖:
(1)互斥洪鸭,共享資源 X 和 Y 只能被一個(gè)線(xiàn)程占用样刷;
(2)占有且等待,線(xiàn)程 T1 已經(jīng)取得共享資源 X览爵,在等待共享資源 Y 的時(shí)候置鼻,不釋放共享資源 X;
(3)不可搶占蜓竹,其他線(xiàn)程不能強(qiáng)行搶占線(xiàn)程 T1 占有的資源箕母;
(4)循環(huán)等待,線(xiàn)程 T1 等待線(xiàn)程 T2 占有的資源俱济,線(xiàn)程 T2 等待線(xiàn)程 T1 占有的資源嘶是,就是循環(huán)等待。
1.4?用“等待-通知”機(jī)制優(yōu)化循環(huán)等待
在 Java 語(yǔ)言里姨蝴,等待 - 通知機(jī)制可以有多種實(shí)現(xiàn)方式俊啼,比如 Java 語(yǔ)言?xún)?nèi)置的 synchronized 配合 wait()、notify()左医、notifyAll() 這三個(gè)方法就能輕松實(shí)現(xiàn)授帕。如果 synchronized 鎖定的是 this,那么對(duì)應(yīng)的一定是 this.wait()浮梢、this.notify()跛十、this.notifyAll();如果 synchronized 鎖定的是 target秕硝,那么對(duì)應(yīng)的一定是 target.wait()芥映、target.notify()、target.notifyAll() 远豺。notify() 是會(huì)隨機(jī)地通知等待隊(duì)列中的一個(gè)線(xiàn)程奈偏,而 notifyAll() 會(huì)通知等待隊(duì)列中的所有線(xiàn)程。有個(gè)阿姆達(dá)爾(Amdahl)定律躯护,代表了處理器并行運(yùn)算之后效率提升的能力惊来,它正好可以解決這個(gè)問(wèn)題,具體公式如下:
公式里的 n 可以理解為 CPU 的核數(shù)棺滞,p 可以理解為并行百分比裁蚁。
1.5 Java線(xiàn)程
線(xiàn)程在sleep期間被打斷了矢渊,拋出一個(gè)InterruptedException異常,try catch捕捉此異常枉证,應(yīng)該重置一下中斷標(biāo)示矮男,因?yàn)閽伋霎惓:螅袛鄻?biāo)示會(huì)自動(dòng)清除掉室谚!
Thread th = Thread.currentThread();
while(true) {
??if(th.isInterrupted()) {
????break;
??}
??// 省略業(yè)務(wù)代碼無(wú)數(shù)
??try {
????Thread.sleep(100);
??}catch (InterruptedException e){
????Thread.currentThread().interrupt();
????e.printStackTrace();
??}
}
CPU 密集型計(jì)算:理論上“線(xiàn)程的數(shù)量 =CPU 核數(shù)”就是最合適的毡鉴。不過(guò)在工程上,線(xiàn)程的數(shù)量一般會(huì)設(shè)置為“CPU 核數(shù) +1”秒赤,這樣的話(huà)眨补,當(dāng)線(xiàn)程因?yàn)榕紶柕膬?nèi)存頁(yè)失效或其他原因?qū)е伦枞麜r(shí),這個(gè)額外的線(xiàn)程可以頂上倒脓,從而保證 CPU 的利用率。
?I/O 密集型:最佳線(xiàn)程數(shù) =CPU 核數(shù) * [ 1 +(I/O 耗時(shí) / CPU 耗時(shí))]
Java不支持尾遞歸含思,盡量不要使用遞歸崎弃。
2.并發(fā)工具類(lèi)
3.并發(fā)設(shè)計(jì)模式
每個(gè)Thread線(xiàn)程內(nèi)部都有一個(gè)Map,Map里面存儲(chǔ)線(xiàn)程本地對(duì)象(key)和線(xiàn)程的變量副本(value)含潘,但是饲做,Thread內(nèi)部的Map是由ThreadLocal維護(hù)的,由ThreadLocal負(fù)責(zé)向map獲取和設(shè)置線(xiàn)程的變量值遏弱。
4.并發(fā)編程案例
4.1?高性能限流器Guava RateLimiter
令牌桶算法是定時(shí)向令牌桶發(fā)送令牌盆均,請(qǐng)求能夠從令牌桶中拿到令牌,然后才能通過(guò)限流器漱逸;而漏桶算法里泪姨,請(qǐng)求就像水一樣注入漏桶,漏桶會(huì)按照一定的速率自動(dòng)將水漏掉饰抒,只有漏桶里還能注入水的時(shí)候肮砾,請(qǐng)求才能通過(guò)限流器。Guava RateLimiter采用令牌桶算法袋坑。
4.2?高性能網(wǎng)絡(luò)應(yīng)用框架Netty
Netty 是一個(gè)款優(yōu)秀的網(wǎng)絡(luò)編程框架仗处,性能非常好,為了實(shí)現(xiàn)高性能的目標(biāo)枣宫,Netty 做了很多優(yōu)化婆誓,例如優(yōu)化了 ByteBuffer、支持零拷貝等等也颤,和并發(fā)編程相關(guān)的就是它的線(xiàn)程模型洋幻。
4.3?高性能隊(duì)列Disruptor
Disruptor 是一款高性能的有界內(nèi)存隊(duì)列,基于Disruptor開(kāi)發(fā)的系統(tǒng)單線(xiàn)程能支撐每秒600萬(wàn)訂單歇拆。原因如下:
(1)內(nèi)存分配更加合理鞋屈,使用 RingBuffer 數(shù)據(jù)結(jié)構(gòu)范咨,數(shù)組元素在初始化時(shí)一次性全部創(chuàng)建,提升緩存命中率厂庇;對(duì)象循環(huán)利用渠啊,避免頻繁 GC。
(2)能夠避免偽共享权旷,提升緩存利用率替蛉。
(3)采用無(wú)鎖算法,避免頻繁加鎖拄氯、解鎖的性能消耗躲查。
(4)支持批量消費(fèi),消費(fèi)者可以無(wú)鎖方式消費(fèi)多個(gè)消息译柏。
Disruptor詳細(xì)介紹:高性能隊(duì)列Disruptor?很詳細(xì)镣煮。
4.4 高性能數(shù)據(jù)庫(kù)連接池HiKariCP
HiKariCP 號(hào)稱(chēng)是業(yè)界跑得最快的數(shù)據(jù)庫(kù)連接池,主要是有兩個(gè)特殊的數(shù)據(jù)結(jié)構(gòu):FastList鄙麦、ConcurrentBag典唇。
1.FastList將remove(Object element)?方法的查找順序變成了逆序查找;get(int index)?方法沒(méi)有對(duì) index 參數(shù)進(jìn)行越界檢查胯府,HiKariCP 能保證不會(huì)越界
2.ConcurrentBag 通過(guò) ThreadLocal 做一次預(yù)分配介衔,避免直接競(jìng)爭(zhēng)共享資源,非常適合池化資源的分配
4.5?Actor模型
Actor模型基于消息機(jī)制骂因,可以實(shí)現(xiàn)并發(fā)編程和分布式編程炎咖。
4.6?軟件事務(wù)內(nèi)存
MVCC(全稱(chēng)是 Multi-Version Concurrency Control),也就是多版本并發(fā)控制寒波,具體代表是:Multiverse