引言
最近還是一直在準(zhǔn)備面試這塊的內(nèi)容泥兰,大多數(shù)互聯(lián)網(wǎng)公司面試笑旺,避免不了的會問到并發(fā)這塊的內(nèi)容,所以好唯,準(zhǔn)備寫幾篇Blog來總結(jié)一下Java并發(fā)方面知識竭沫,于是就先寫一篇最基礎(chǔ)的,也就是并發(fā)編程中涉及到的基礎(chǔ)概念渠啊,因為主要是想到什么寫什么输吏,所以可能會有遺漏,就當(dāng)看個熱鬧吧替蛉。
這邊文章參考了網(wǎng)上眾多文章贯溅,并加上了自己的理解,也可能會有理解不周到的地方躲查,如有錯誤它浅,請及時指出。
進(jìn)程和線程
定義
進(jìn)程(Process):具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合上的一次運行活動镣煮,進(jìn)程是系統(tǒng)進(jìn)行資源分配的一個獨立單位(PS:若不支持線程機(jī)制姐霍,進(jìn)程也是系統(tǒng)調(diào)度的單位。否則,線程是系統(tǒng)調(diào)度的單位镊折。因為隨著cpu的性能越來越好胯府,所以才將資源分配和調(diào)度分開,于是才有了線程這個概念)恨胚。比如我們打開一個程序骂因,這個程序就是一個進(jìn)程。
線程(Thread or Lightweight Process LWP):從其英文定義可以看出赃泡,線程也可以被稱之為輕量級進(jìn)程寒波。線程是進(jìn)程的一個實體,是程序執(zhí)行流的最小單元升熊。
通常將進(jìn)程看作是分配資源的最小單位俄烁,而線程則是操作系統(tǒng)調(diào)度的最小單位。線程自己基本上不擁有系統(tǒng)資源,只擁有一點在運行中必不可少的資源(如程序計數(shù)器,一組寄存器和棧),但是它可與同屬一個進(jìn)程的其他的線程共享進(jìn)程所擁有的全部資源级野。
進(jìn)程和線程的關(guān)系以及區(qū)別
1.線程是進(jìn)程的組成部分页屠,一個進(jìn)程可以擁有很多線程,但至少有一個線程蓖柔,但是一個線程只能屬于一個進(jìn)程卷中;
2.操作系統(tǒng)的資源是分配給進(jìn)程的,同一進(jìn)程內(nèi)的線程共享該進(jìn)程的所有資源,但CPU是分配給線程的渊抽,即真正在處理及上運行的是線程;
3议忽、不同的進(jìn)程使用不同的內(nèi)存空間懒闷,而一個進(jìn)程內(nèi)的所有線程共享父進(jìn)程所擁有的全部空間,這極大提高了程序的運行效率(PS:內(nèi)存空間不同于椪恍遥空間愤估,每個線程都擁有單獨的棧內(nèi)存用來存儲本地數(shù)據(jù)。線程擁有自己的堆棧速址、自己的程序計數(shù)器和自己的局部變量玩焰,但他不擁有系統(tǒng)資源);
4.線程的調(diào)度和管理由進(jìn)程本身負(fù)責(zé)完成芍锚。操作系統(tǒng)對進(jìn)程進(jìn)行調(diào)度昔园,管理和資源分配;
5.同一個進(jìn)程的線程之間可以直接通信并炮,但是進(jìn)程之間的交流需要借助中間代理來實現(xiàn)默刚;
6.一個線程可以操作同一進(jìn)程的其他線程,但是進(jìn)程只能操作其子進(jìn)程;
7.線程的啟動速度快逃魄,進(jìn)程的啟動速度慢荤西。
協(xié)程
Java原生并不提供對協(xié)程的支持,但有的框架模擬出了協(xié)程的功能,比如Kilim框架邪锌,這里我們就簡單提一下協(xié)程的概念勉躺。
協(xié)程(Coroutines),是一種比線程更加輕量級的存在觅丰。一個進(jìn)程可以擁有多個線程饵溅,一個線程也可以擁有多個協(xié)程。
協(xié)程的切換不是被操作系統(tǒng)的內(nèi)核管理舶胀,而是直接由程序控制
線程進(jìn)程都是同步機(jī)制概说,而協(xié)程則是異步。協(xié)程能保留上一次調(diào)用時的狀態(tài)嚣伐,每次過程重入時糖赔,就相當(dāng)于進(jìn)入上一次調(diào)用的狀態(tài)。
協(xié)程的暫停完全由程序控制轩端,而線程之間狀態(tài)的切換是由操作系統(tǒng)的內(nèi)核實現(xiàn)的放典,所以協(xié)程的開銷遠(yuǎn)遠(yuǎn)小于線程的開銷。
多進(jìn)程和多線程
就拿我們平時使用電腦來舉個例子:
1.我們打開chrome瀏覽器基茵,用瀏覽器一邊下載文件奋构、一邊聽歌、一邊看網(wǎng)頁拱层,這就是多線程弥臼;
2.我們同時打開了QQ、微信根灯、瀏覽器等径缅,這就是多進(jìn)程。
多進(jìn)程和多線程的比較
從上面例子烙肺,我們從來分析一下多進(jìn)程和多線程之間的優(yōu)劣勢
對比維度 | 多進(jìn)程 | 多線程 | 總結(jié) |
---|---|---|---|
數(shù)據(jù)共享纳猪、同步 | 數(shù)據(jù)共享復(fù)雜,需要用IPC桃笙;數(shù)據(jù)是分開的氏堤,同步簡單 | 因為共享進(jìn)程數(shù)據(jù),數(shù)據(jù)共享簡單搏明,但也是因為這個原因?qū)е峦綇?fù)雜 | 各有優(yōu)勢 |
內(nèi)存鼠锈、CPU | 占用內(nèi)存多,切換復(fù)雜星著,CPU利用率低 | 占用內(nèi)存少脚祟,切換簡單,CPU利用率高 | 線程占優(yōu) |
數(shù)據(jù)共享强饮、同步 | 數(shù)據(jù)共享復(fù)雜由桌,需要用IPC;數(shù)據(jù)是分開的,同步簡單 | 因為共享進(jìn)程數(shù)據(jù)行您,數(shù)據(jù)共享簡單铭乾,但也是因為這個原因?qū)е峦綇?fù)雜 | 各有優(yōu)勢 |
創(chuàng)建銷毀、切換 | 創(chuàng)建銷毀娃循、切換復(fù)雜炕檩,速度慢 | 創(chuàng)建銷毀、切換簡單捌斧,速度很快 | 線程占優(yōu) |
編程笛质、調(diào)試 | 編程簡單,調(diào)試簡單 | 編程復(fù)雜捞蚂,調(diào)試復(fù)雜 | 進(jìn)程占優(yōu) |
可靠性 | 進(jìn)程間不會相互影響 | 因為共享進(jìn)程數(shù)據(jù)妇押,所以導(dǎo)致一旦一個線程掛掉,整個進(jìn)程也會隨之掛掉 | 進(jìn)程占優(yōu) |
分布式 | 適應(yīng)于多核姓迅、多機(jī)分布式敲霍;如果一臺機(jī)器不夠,擴(kuò)展到多臺機(jī)器比較簡單 | 適應(yīng)于多核分布式 | 進(jìn)程占優(yōu) |
多線程的好處
1.同一進(jìn)程的線程之間共享內(nèi)存丁存,但是進(jìn)程之間不能共享內(nèi)存肩杈;
2.系統(tǒng)創(chuàng)建進(jìn)程需要重新為其分配系統(tǒng)資源,但是線程并不需要解寝,所以代價會小很多扩然,效率也會更高;
3.資源利用率更好与学;
4.程序設(shè)計更加簡單;
5.程序響應(yīng)更快晕窑。
這里可以參考并發(fā)編程網(wǎng)
總而言之,隨著CPU性能的越來越好,多線程可以讓我們充分利用CPU的資源,幫助我們編寫出高性能高效率的程序一汽。
并發(fā)和并行
介紹
從時間上來說,并行是指兩個或多個時間在同一時刻同時發(fā)生,而并發(fā)則是指兩個或多個事件在同一時間段內(nèi)同時發(fā)生隶债;
從空間上來說,并行實在多個不同實體上進(jìn)行的笤虫,而并發(fā)是在同一個實體上進(jìn)行的遭庶。
并發(fā)編程的目標(biāo)是充分的利用處理器的每一個核峦睡,以達(dá)到最高的處理性能。
圖片解釋
現(xiàn)實場景
舉個驚悚點的例子:
普通人只有1個腦子,一邊吃飯一邊說話瓜富,其實有腦子有任務(wù)切換在里面鳍咱,所以是并發(fā)。(PS:只是因為這個切換速度非秤敫蹋快谤辜,所以讓人產(chǎn)生了是并行的感覺)
一個雙腦人,一個腦吃飯价捧,一個腦說話丑念,倆腦子彼此獨立互不沖突,這是并行结蟋。
一個雙腦人脯倚,一個腦子吃飯+看書,一個腦子說話嵌屎,這是并行中含有并發(fā)(有個腦子在輪換吃飯 和 看書)推正。
一個三腦人,一個腦子吃飯宝惰,一個腦子看書植榕,一個腦子說話,這是并行尼夺。
讓一個CPU執(zhí)行多個【可并行 ( 可分解尊残,可單獨核心運行)】 的任務(wù),就是并發(fā)(concurrence)
多個CPU分別執(zhí)行【可并行】任務(wù)汞斧,就是并行(parallel)
在現(xiàn)實中
單核的機(jī)器,都是并發(fā) concurrence 執(zhí)行的什燕。
多核的機(jī)器粘勒,都是并行 parallel 中嵌套著并發(fā) concurrence 運行的。
如果存在無限核心的機(jī)器屎即,則所有任務(wù)都可以是并行運行的庙睡。
臨界區(qū)
介紹
臨界區(qū)用來表示一種公共資源或者說是共享數(shù)據(jù)事富,可以被多個線程使用。但是每一次只能有一個線程使用它乘陪,一旦臨界區(qū)資源被占用统台,其他線程要想使用這個資源,就必須等待啡邑。
畫圖解釋
現(xiàn)實場景
打印機(jī)贱勃,我們工作的時候肯定會經(jīng)常用到打印機(jī),打印機(jī)就是一個臨界區(qū)的最好例子谤逼。一臺打印機(jī)一次只能執(zhí)行一個任務(wù)贵扰,如果 A 和 B 同時需要打印文件,很顯然流部,如果 A 先發(fā)下打印的任務(wù)戚绕,打印機(jī)就開始打印 A 的文件。B 的任務(wù)就只能等待 A 打印結(jié)束之后才能打印枝冀。
同步和異步舞丛、阻塞和非阻塞
同步和異步
介紹
同步和異步通常用來形容一次方法的調(diào)用。
同步方法調(diào)用一旦開始果漾,調(diào)用者必須等到方法調(diào)用返回后球切,才能繼續(xù)后續(xù)的行為。
異步方法調(diào)用更像一個消息傳遞跨晴,一旦開始欧聘,方法調(diào)用就會立即返回,但調(diào)用者并不一定會得到結(jié)果端盆,而且調(diào)用者可以繼續(xù)后續(xù)的操作怀骤。
圖片解釋
阻塞和非阻塞
這里把同步異步和阻塞非阻塞放在一起,主要是很多時候?qū)@幾個概念有點模糊焕妙,這里具體說一下蒋伦。
同步和異步關(guān)心的是消息傳輸機(jī)制,而阻塞非阻塞關(guān)心的是程序在等待調(diào)用結(jié)果返回時的狀態(tài)焚鹊。
所以上面四種概念痕届,會有四種組合方式,分別是:
同步阻塞末患、同步非阻塞研叫、異步阻塞和異步非阻塞。
現(xiàn)實場景
這樣講會可能比較抽象璧针,那我們繼續(xù)結(jié)合實際來說一下嚷炉,首先是燒水的例子,我們擁有兩個水壺探橱,一個是會響的水壺申屹,簡稱響水壺绘证,一個是普通水壺。
1.用普通水壺煮水哗讥,并且站在那里嚷那,不管水開沒開,每隔一定時間看看水開了沒杆煞。-同步阻塞
2.還是用普通水壺煮水魏宽,不再傻傻的站在那里看水開,跑去寢室上網(wǎng)索绪,但是還是會每隔一段時間過來看看水開了沒有湖员,水沒有開就走人。-同步非阻塞
3.使用高大上的響水壺來煮水瑞驱,站在那里娘摔,但是不會再每隔一段時間去看水開,而是等水開了唤反,水壺會自動的通知他凳寺。-異步阻塞
4.還是使用響水壺煮水,跑到客廳上網(wǎng)去彤侍,等著響水壺自己把水煮熟了以后通知他肠缨。-異步非阻塞
這里可以看出
同步就是燒開水,需要自己去輪詢(每隔一段時間去看看水開了沒)盏阶,異步就是水開了晒奕,然后水壺會通知你水已經(jīng)開了,你可以回來處理這些開水了名斟。
同步和異步是相對于操作結(jié)果來說脑慧,就是會不會等待結(jié)果返回。
阻塞就是說在煮水的過程中砰盐,你不可以去干其他的事情闷袒,非阻塞就是在同樣的情況下,可以同時去干其他的事情岩梳。阻塞和非阻塞是相對于線程是否被阻塞囊骤。
再講一個下載的例子:
1.同步阻塞:一直盯著下載進(jìn)度條,到 100% 的時候就完成冀值。
同步體現(xiàn)在:等待下載完成通知也物;
阻塞體現(xiàn)在:等待下載完成通知過程中,不能做其他任務(wù)處理列疗;
2.同步非阻塞:小明提交下載任務(wù)后就去干別的滑蚯,每過一段時間就去瞄一眼進(jìn)度條,看到 100% 就完成作彤。
同步體現(xiàn)在:等待下載完成通知膘魄;
非阻塞體現(xiàn)在:等待下載完成通知過程中,去干別的任務(wù)了竭讳,只是時不時會瞄一眼進(jìn)度條创葡;【小明必須要在兩個任務(wù)間切換,關(guān)注下載進(jìn)度】
3.異步阻塞:小明換了個有下載完成通知功能的軟件绢慢,下載完成就“叮”一聲。不過小明仍然一直等待“栋墙眨”的聲音蕴掏。
異步體現(xiàn)在:下載完成“叮”一聲通知缚窿;
阻塞體現(xiàn)在:等待下載完成“都遥”一聲通知過程中,不能做其他任務(wù)處理倦零;
4.異步非阻塞:仍然是那個會“段笮”一聲的下載軟件,小明提交下載任務(wù)后就去干別的扫茅,聽到“短G叮”的一聲就知道完成了。
異步體現(xiàn)在:下載完成“逗叮”一聲通知栽烂;
非阻塞體現(xiàn)在:等待下載完成“叮”一聲通知過程中恋脚,去干別的任務(wù)了腺办,只需要接收“叮”聲通知即可慧起;【軟件處理下載任務(wù)菇晃,小明處理其他任務(wù),不需關(guān)注進(jìn)度蚓挤,只需接收軟件“痘撬停”聲通知,即可】
同步和異步關(guān)注的是消息如何通知的機(jī)制灿意,而阻塞和非阻塞關(guān)注的則是等待消息通知時的狀態(tài)估灿,這兩個概念是理解同步、異步缤剧、阻塞馅袁、非阻塞的關(guān)鍵所在。
死鎖荒辕、饑餓汗销、活鎖
死鎖
介紹
死鎖犹褒,是指兩個或兩個以上的進(jìn)程在執(zhí)行過程中,由于競爭資源或者由于彼此通信而造成的一種阻塞的現(xiàn)象弛针,若無外力作用叠骑,它們都將無法推進(jìn)下去。此時稱系統(tǒng)處于死鎖狀態(tài)或系統(tǒng)產(chǎn)生了死鎖削茁,這些永遠(yuǎn)在互相等待的進(jìn)程稱為死鎖進(jìn)程宙枷。
例如,線程A已經(jīng)獲取資源R1茧跋,線程B已經(jīng)獲取資源R2慰丛,之后線程A嘗試獲取資源R2,這個時候因為資源R2已經(jīng)被線程B獲得了瘾杭,所以線程A只能阻塞直到線程B釋放資源R2诅病。另一方面,線程B在已經(jīng)獲得資源R2的前提下嘗試獲取由線程A持有的資源R1粥烁,那么由于資源R1已經(jīng)被線程A持有了睬隶,那么線程B只能被阻塞直到線程A釋放資源R1。這樣線程A和線程B都在等待對方持有的資源页徐,就造成了死鎖苏潜。
死鎖的四個條件
1.互斥條件:線程使用的資源必須至少有一個是不能共享的(至少有鎖);
2.請求與保持條件:至少有一個線程必須持有一個資源并且正在等待獲取一個當(dāng)前被其它線程持有的資源(至少兩個線程持有不同鎖变勇,又在等待對方持有鎖)恤左;
3.非剝奪條件:分配資源不能從相應(yīng)的線程中被強(qiáng)制剝奪(不能強(qiáng)行獲取被其他線程持有鎖);
4.循環(huán)等待條件:第一個線程等待其它線程搀绣,后者又在等待第一個線程(線程A等線程B飞袋;線程B等線程C;...;線程N(yùn)等線程A。如此形成環(huán)路)链患。
避免死鎖的方法
1.盡可能減少鎖的范圍巧鸭,如在Java中盡量使用同步代碼塊而不使用同步方法;
2.盡量不編寫在同一時刻獲取多個鎖的代碼麻捻,因為在一個線程持有多個資源的時候很容易發(fā)生死鎖纲仍;
3.根據(jù)情況將過大范圍的鎖進(jìn)行切分,讓每個鎖的作用范圍減小贸毕,從而降低死鎖發(fā)生的概率郑叠。這以原則的典型應(yīng)用是ConcurrentHashMap的鎖分段技術(shù)。
饑餓
饑餓是指某一個或者多個線程因為種種原因無法獲得所需要的資源明棍,導(dǎo)致一直無法執(zhí)行乡革。
產(chǎn)生這種情況的原因是多種的,可能是它的線程優(yōu)先級太低,而高優(yōu)先級的線程不搶占它需要的資源沸版,導(dǎo)致低優(yōu)先級線程無法工作嘁傀。也可能是某個線程一直占著關(guān)鍵資源不放,導(dǎo)致其他需要這個資源的線程無法正常執(zhí)行视粮。
活鎖
活鎖指的是線程不斷重復(fù)執(zhí)行相同的操作心包,但每次操作的結(jié)果都是失敗的。盡管這個問題不會阻塞線程馒铃,但是程序也無法繼續(xù)執(zhí)行。
舉個現(xiàn)實生活中的例子痕惋,一條狹窄的道路区宇,男子A和女子B迎面相遇了,緣分使然值戳,A紳士的想讓出道路讓B先過议谷,這時B也保持良好的淑女形象想讓出道路讓A先過,導(dǎo)致兩人都避讓了堕虹;兩人尷尬一笑卧晓,之后A想著B既然讓了,就準(zhǔn)備先過赴捞,巧的是逼裆,這時B心里也想著,A既然讓了赦政,不如先過胜宇,兩人又撞上了;然后又開始禮貌性的相互避讓恢着,避讓之后各自又想先走結(jié)果又撞上了桐愉,結(jié)果兩人都沒過去。這種情況就是活鎖掰派。
線程都秉承著"謙讓"的原則从诲,主動將資源釋放給他人使用,那么就會出現(xiàn)資源不斷在兩個線程之間跳動靡羡,而沒有一個線程可以同時拿到所有資源而正常執(zhí)行系洛。
活鎖通常發(fā)生在處理事務(wù)消息的應(yīng)用程序中,如果不能成功處理這個事務(wù)那么事務(wù)將回滾整個操作略步。解決活鎖的辦法是在每次重復(fù)執(zhí)行的時候引入隨機(jī)機(jī)制碎罚,這樣由于出現(xiàn)的可能性不同使得程序可以繼續(xù)執(zhí)行其他的任務(wù)。
參考
本文參考眾多文章總計而成纳像,這里參考太多荆烈,所以只列舉比較重要的幾篇文章。
Java多線程和并發(fā)性知識點總結(jié)
并發(fā)編程的幾個概念
聊聊同步、異步憔购、阻塞與非阻塞