? 進程莽囤、線程和多線程
進程:
定義:進程是一個具有一定獨立功能的程序關(guān)于某個數(shù)據(jù)集合的一次運行活動。它是操作系統(tǒng)動態(tài)執(zhí)行的基本單元,在傳統(tǒng)的操作系統(tǒng)中羡宙,進程既是基本的分配單元,也是基本的執(zhí)行單元掐隐。
- 第一狗热,進程是一個實體。每一個進程都有它自己的地址空間虑省,一般情況下匿刮,包括文本區(qū)域(text region)、數(shù)據(jù)區(qū)域(data region)和堆棧(stack region)探颈。文本區(qū)域存儲處理器執(zhí)行的代碼熟丸;數(shù)據(jù)區(qū)域存儲變量和進程執(zhí)行期間使用的動態(tài)分配的內(nèi)存;堆棧區(qū)域存儲著活動過程調(diào)用的指令和本地變量伪节。
- 第二光羞,進程是一個“執(zhí)行中的程序”。程序是一個沒有生命的實體怀大,只有處理器賦予程序生命時纱兑,它才能成為一個活動的實體,我們稱其為進程化借。
狀態(tài):就緒潜慎、運行和阻塞
就緒狀態(tài)其實就是獲取了出cpu外的所有資源,在隊列中等待蓖康;運行就是獲得了處理器分配的資源铐炫,程序開始執(zhí)行;阻塞態(tài)蒜焊,當(dāng)程序條件不夠時候倒信,需要等待條件滿足時候才能執(zhí)行。
線程:
一個進程中可以包含若干個線程泳梆,當(dāng)然一個進程中至少有一個線程鳖悠,把線程作為獨立運行和獨立調(diào)度的基本單位唆迁,由于線程比進程更小,基本上不擁有系統(tǒng)資源竞穷,故對它的調(diào)度所付出的開銷就會小得多唐责,能更高效的提高系統(tǒng)多個程序間并發(fā)執(zhí)行的程度。
java創(chuàng)建線程三種方式:
- 繼承Thread類創(chuàng)建線程
- 實現(xiàn)Runnable接口創(chuàng)建線程
- 使用Callable和Future創(chuàng)建線程
1.創(chuàng)建Callable接口的實現(xiàn)類瘾带,并實現(xiàn)call()方法鼠哥,該call()方法將作為線程執(zhí)行體,且該call()方法沒有返回值看政,再創(chuàng)建Callable實現(xiàn)類的實例朴恳。(從java8開始,可以直接使用Lambda表達式創(chuàng)建Callable對象)允蚣。
2.使用FutureTask類來包裝Callable對象于颖,該FutureTask對象封裝了該Callable對象的call()方法的返回值。
3.使用FutureTask作為Thread對象的target創(chuàng)建并啟動新線程嚷兔。
4.調(diào)用FutureTask對象的get方法來獲得子線程執(zhí)行結(jié)束后的返回值森渐。
多線程:
定義:指的是這個程序(一個進程)運行時產(chǎn)生了不止一個線程。
- 并行:多個cpu實例或者多臺機器同時執(zhí)行一段處理邏輯冒晰,是真正的同時同衣。
- 并發(fā):通過cpu調(diào)度算法,讓用戶看上去同時執(zhí)行壶运,實際上從cpu操作層面不是真正的同時耐齐。并發(fā)往往在場景中有公用的資源,那么針對這個公用的資源往往產(chǎn)生瓶頸蒋情,我們會用TPS或者QPS來反應(yīng)這個系統(tǒng)的處理能力埠况。
- 線程安全:經(jīng)常用來描繪一段代碼。指在并發(fā)的情況之下棵癣,該代碼經(jīng)過多線程使用辕翰,線程的調(diào)度順序不影響任何結(jié)果。這個時候使用多線程浙巫,我們只需要關(guān)注系統(tǒng)的內(nèi)存金蜀,cpu是不是夠用即可刷后。反過來的畴,線程不安全就意味著線程的調(diào)度順序會影響最終結(jié)果,如不加事務(wù)的轉(zhuǎn)賬代碼尝胆。
- 同步:Java中的同步指的是通過人為的控制和調(diào)度丧裁,保證共享資源的多線程訪問成為線程安全,來保證結(jié)果的準(zhǔn)確含衔。如上面的代碼簡單加入@synchronized關(guān)鍵字煎娇。在保證結(jié)果準(zhǔn)確的同時二庵,提高性能,才是優(yōu)秀的程序缓呛。線程安全的優(yōu)先級高于性能催享。
線程的狀態(tài):
- 新建狀態(tài)(New) : 線程對象被創(chuàng)建后,就進入了新建狀態(tài)哟绊。例如因妙,Thread thread = new Thread()。
- 就緒狀態(tài)(Runnable): 也被稱為“可執(zhí)行狀態(tài)”票髓。線程對象被創(chuàng)建后攀涵,其它線程調(diào)用了該對象的start()方法,從而來啟動該線程洽沟。例如以故,thread.start()。處于就緒狀態(tài)的線程裆操,隨時可能被CPU調(diào)度執(zhí)行怒详。
- 運行狀態(tài)(Running) : 線程獲取CPU權(quán)限進行執(zhí)行。需要注意的是踪区,線程只能從就緒狀態(tài)進入到運行狀態(tài)棘利。
- 阻塞狀態(tài)(Blocked) : 阻塞狀態(tài)是線程因為某種原因放棄CPU使用權(quán),暫時停止運行朽缴。直到線程進入就緒狀態(tài)善玫,才有機會轉(zhuǎn)到運行狀態(tài)。阻塞的情況分三種:
(01) 等待阻塞 -- 通過調(diào)用線程的wait()方法密强,讓線程等待某工作的完成茅郎。
(02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因為鎖被其它線程所占用),它會進入同步阻塞狀態(tài)或渤。
(03) 其他阻塞 -- 通過調(diào)用線程的sleep()或join()或發(fā)出了I/O請求時饰剥,線程會進入到阻塞狀態(tài)喜爷。當(dāng)sleep()狀態(tài)超時、join()等待線程終止或者超時、或者I/O處理完畢時濒旦,線程重新轉(zhuǎn)入就緒狀態(tài)。 - 死亡狀態(tài)(Dead) : 線程執(zhí)行完了或者因異常退出了run()方法平窘,該線程結(jié)束生命周期炉旷。
高級多線程控制類:
- ThreadLocal類
用處:保存線程的獨立變量。對一個線程類(繼承自Thread)
當(dāng)使用ThreadLocal維護變量時地熄,ThreadLocal為每個使用該變量的線程提供獨立的變量副本华临,所以每一個線程都可以獨立地改變自己的副本,而不會影響其它線程所對應(yīng)的副本端考。常用于用戶登錄控制雅潭,如記錄session信息揭厚。
實現(xiàn):每個Thread都持有一個TreadLocalMap類型的變量(該類是一個輕量級的Map,功能與map一樣扶供,區(qū)別是桶里放的是entry而不是entry的鏈表筛圆。功能還是一個map。)以本身為key椿浓,以目標(biāo)為value顽染。
主要方法是get()和set(T a),set之后在map里維護一個threadLocal -> a轰绵,get時將a返回粉寞。ThreadLocal是一個特殊的容器。 - Lock類
三個實現(xiàn):
ReentrantLock:
可重入的意義在于持有鎖的線程可以繼續(xù)持有左腔,并且要釋放對等的次數(shù)后才真正釋放該鎖唧垦。
ReentrantReadWriteLock.ReadLock
ReentrantReadWriteLock.WriteLock
lock更靈活,可以自由定義多把鎖的加鎖解鎖順序(synchronized要按照先加的后解順序)
提供多種加鎖方案液样,lock 阻塞式, trylock 無阻塞式, lockInterruptily 可打斷式振亮, 還有trylock的帶超時時間版本。和Condition類的結(jié)合鞭莽。 - BlockingQueue類
阻塞隊列坊秸。該類是java.util.concurrent包下的重要類,通過對Queue的學(xué)習(xí)可以得知澎怒,這個queue是單向隊列褒搔,可以在隊列頭添加元素和在隊尾刪除或取出元素。類似于一個管 道喷面,特別適用于先進先出策略的一些應(yīng)用場景星瘾。普通的queue接口主要實現(xiàn)有PriorityQueue(優(yōu)先隊列)。
獲取線程異常:
線程無法通過try/catch獲取到異常惧辈。
寫的時候最好要設(shè)置線程名稱 Thread.name琳状,并設(shè)置線程組 ThreadGroup,目的是方便管理盒齿。在出現(xiàn)問題的時候念逞,打印線程棧 (jstack -pid) 一眼就可以看出是哪個線程出的問題,這個線程是干什么的边翁。
線程池:
線程池作用就是限制系統(tǒng)中執(zhí)行線程的數(shù)量翎承。
ThreadPoolExecutor:
常用的有四種:
- newSingleThreadExecutor (創(chuàng)建一個單線程的線程池。)
- newFixedThreadPool (創(chuàng)建固定大小的線程池倒彰。)
- newCachedThreadPool (創(chuàng)建一個可緩存的線程池审洞。)
- newScheduledThreadPool (創(chuàng)建一個支持定時以及周期性執(zhí)行任務(wù)的線程池。)
設(shè)置線程池:限制最大線程待讳,如果是CPU密集型的任務(wù)芒澜,核心線程就用核數(shù),最大線程就用核數(shù)的N倍
什么時候出現(xiàn)僵死進程
當(dāng)一個進程創(chuàng)建了一個子進程時创淡,他們的運行時異步的痴晦。即父進程無法預(yù)知子進程會在什么時候結(jié)束,那么如果父進程很繁忙來不及wait 子進程時琳彩,那么當(dāng)子進程結(jié)束時誊酌,會不會丟失子進程的結(jié)束時的狀態(tài)信息呢?處于這種考慮unix提供了一種機制可以保證只要父進程想知道子進程結(jié)束時的信息露乏,它就可以得到碧浊。
這種機制是:在每個進程退出的時候,內(nèi)核釋放該進程所有的資源瘟仿,包括打開的文件箱锐,占用的內(nèi)存。但是仍然保留了一些信息(如進程號pid 退出狀態(tài) 運行時間等)劳较。這些保留的信息直到進程通過調(diào)用wait/waitpid時才會釋放驹止。這樣就導(dǎo)致了一個問題,如果沒有調(diào)用wait/waitpid的話观蜗,那么保留的信息就不會釋放臊恋。比如進程號就會被一直占用了。但系統(tǒng)所能使用的進程號的有限的墓捻,如果產(chǎn)生大量的僵尸進程抖仅,將導(dǎo)致系統(tǒng)沒有可用的進程號而導(dǎo)致系統(tǒng)不能創(chuàng)建進程。
如果父進程先結(jié)束砖第,而子進程后結(jié)束岸售,且沒有調(diào)用wait/waitpid來等待子進程的結(jié)束,每個進程結(jié)束時厂画,系統(tǒng)都會掃描當(dāng)前系統(tǒng)中運行的所有進程凸丸,看看有沒有哪個進程時剛剛結(jié)束的這個進程的子進程,如果有袱院,就有init來接管它屎慢,成為它的父進程。
- 即:如果子進程先結(jié)束而父進程后結(jié)束忽洛,即子進程結(jié)束后腻惠,父進程還在繼續(xù)運行但是并未調(diào)用wait/waitpid那子進程就會成為僵尸進程。
如何實現(xiàn)線程安全:
基本上所有的并發(fā)模式在解決線程安全問題上欲虚,都采用“序列化訪問臨界資源”的方案集灌,即在同一時刻,只能有一個線程訪問臨界資源,也稱同步互斥訪問欣喧。通常來說腌零,是在訪問臨界資源的代碼前面加上一個鎖,當(dāng)訪問完臨界資源后釋放鎖唆阿,讓其他線程繼續(xù)訪問益涧。
用Synchronization或者Lock
CAS原子鎖:
CAS有3個操作數(shù),內(nèi)存值V驯鳖,舊的預(yù)期值A(chǔ)闲询,要修改的新值B。當(dāng)且僅當(dāng)預(yù)期值A(chǔ)和內(nèi)存值V相同時浅辙,將內(nèi)存值V修改為B扭弧,否則什么都不做。
TODO:具體還需要研究
ThreadLocal什么時候會出現(xiàn)OOM的情況?為什么?
ThreadLocalMap使用ThreadLocal的弱引用作為key记舆,如果一個ThreadLocal沒有外部強引用來引用它鸽捻,那么系統(tǒng) GC 的時候,這個ThreadLocal勢必會被回收氨淌,這樣一來泊愧,ThreadLocalMap中就會出現(xiàn)key為null的Entry,就沒有辦法訪問這些key為null的Entry的value盛正,如果當(dāng)前線程再遲遲不結(jié)束的話删咱,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永遠無法回收,造成內(nèi)存泄漏豪筝。
其實痰滋,ThreadLocalMap的設(shè)計中已經(jīng)考慮到這種情況,也加上了一些防護措施:在ThreadLocal的get(),set(),remove()的時候都會清除線程ThreadLocalMap里所有key為null的value续崖。
- 使用static的ThreadLocal敲街,延長了ThreadLocal的生命周期,可能導(dǎo)致內(nèi)存泄漏严望。
- 分配使用了ThreadLocal又不再調(diào)用get(),set(),remove()方法多艇,那么就會導(dǎo)致內(nèi)存泄漏,因為這塊內(nèi)存一直存在像吻。