進(jìn)程和線(xiàn)程
都是一段可執(zhí)行的代碼,進(jìn)程是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個(gè)獨(dú)立單位,線(xiàn)程是進(jìn)程的一個(gè)實(shí)體,是CPU調(diào)度和分派的基本單位,它是比進(jìn)程更小的能獨(dú)立運(yùn)行的基本單位,線(xiàn)程自己基本上不擁有系統(tǒng)資源,只擁有一點(diǎn)在運(yùn)行中不可少的資源。一個(gè)進(jìn)程可擁有多個(gè)線(xiàn)程,多個(gè)線(xiàn)程共享這個(gè)進(jìn)程上的全部資源。進(jìn)程和線(xiàn)程都是一個(gè)時(shí)間段的描述,是CPU工作時(shí)間段的描述。
實(shí)現(xiàn)線(xiàn)程的三種方式
使用內(nèi)核線(xiàn)程實(shí)現(xiàn)、使用用戶(hù)線(xiàn)程實(shí)現(xiàn)、使用用戶(hù)線(xiàn)程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)。
內(nèi)核線(xiàn)程:直接由操作系統(tǒng)內(nèi)核支持的線(xiàn)程,內(nèi)核通過(guò)操縱調(diào)度器對(duì)線(xiàn)程進(jìn)行調(diào)度,并負(fù)責(zé)將線(xiàn)程的任務(wù)映射到各個(gè)處理器上,每個(gè)內(nèi)核線(xiàn)程可以視為內(nèi)核的一個(gè)分身,這樣操作系統(tǒng)就有能力同時(shí)處理多件事情,支持多線(xiàn)程的內(nèi)核就叫做多線(xiàn)程內(nèi)核。程序一般不會(huì)直接去使用內(nèi)核線(xiàn)程,而是去使用內(nèi)核線(xiàn)程的一種高級(jí)接口——輕量級(jí)進(jìn)程。輕量級(jí)進(jìn)程就是我們通常意義上所講的線(xiàn)程。
狹義上用戶(hù)線(xiàn)程指完全建立在用戶(hù)空間的線(xiàn)程庫(kù)上,系統(tǒng)內(nèi)核不能感知線(xiàn)程存在的實(shí)現(xiàn)。如果程序?qū)崿F(xiàn)得當(dāng),這種線(xiàn)程不需要切換到內(nèi)核態(tài),因此操作可以是非??焖偾业拖牡摹?/p>
3.使用用戶(hù)線(xiàn)程加輕量級(jí)進(jìn)程混合實(shí)現(xiàn)
線(xiàn)程調(diào)度是指系統(tǒng)的線(xiàn)程分配處理器使用權(quán)的過(guò)程,主要調(diào)度方式有兩種:協(xié)同式線(xiàn)程調(diào)度、搶占式線(xiàn)程調(diào)度。
6種線(xiàn)程狀態(tài):新建(new)、運(yùn)行(Runnable)、無(wú)限期等待(Waiting)、限期等待、阻塞、結(jié)束。
阻塞:“阻塞狀態(tài)”和“等待狀態(tài)”的區(qū)別是:“阻塞狀態(tài)”在等待著獲取到一個(gè)排他鎖,這個(gè)事件將在另外一個(gè)線(xiàn)程放棄這個(gè)鎖的時(shí)候發(fā)生;而“等待狀態(tài)”則是在等待一段時(shí)間,或者喚醒動(dòng)作的發(fā)生,在程序等待進(jìn)入同步區(qū)域的時(shí)候,線(xiàn)程將進(jìn)入這種狀態(tài)。
線(xiàn)程安全與鎖優(yōu)化
java語(yǔ)言中各種操作共享的數(shù)據(jù)分為5類(lèi):不可變、絕對(duì)線(xiàn)程安全、相對(duì)線(xiàn)程安全、線(xiàn)程兼容和線(xiàn)程對(duì)立。
相對(duì)線(xiàn)程安全:我們?cè)谡{(diào)用的時(shí)候不需要做額外的保障措施,但是對(duì)于一些特定順序的連續(xù)調(diào)用,就可能需要在調(diào)用端使用額外的同步手段來(lái)保證調(diào)用的正確性。
線(xiàn)程兼容:指對(duì)象本身并不是線(xiàn)程安全的,但是可以通過(guò)在調(diào)用端正確地使用同步手段來(lái)保證對(duì)象在并發(fā)環(huán)境中可以安全地使用。
1)互斥同步:同步是指在多個(gè)線(xiàn)程并發(fā)訪(fǎng)問(wèn)共享數(shù)據(jù)時(shí),保證共享數(shù)據(jù)在同一個(gè)時(shí)刻只被一個(gè)線(xiàn)程使用,而互斥是實(shí)現(xiàn)同步的一種手段,臨界區(qū)、互斥量和信號(hào)量都是主要的互斥實(shí)現(xiàn)方式。synchronized同步塊對(duì)同一條線(xiàn)程來(lái)說(shuō)是可重入的,不會(huì)出現(xiàn)自己把自己鎖死的問(wèn)題,其次,同步塊在已進(jìn)入的線(xiàn)程執(zhí)行完之前,會(huì)阻塞后面其他線(xiàn)程的進(jìn)入。
除了synchronized,還可以使用java.util.concurrent包中的重入鎖(Reentrantlock)來(lái)實(shí)現(xiàn)同步。
2)非阻塞同步
3)無(wú)同步方案:1、可重入代碼:相對(duì)線(xiàn)程安全來(lái)說(shuō),可重入更基本的特征,它可以保證線(xiàn)程安全,即所有的可重入的代碼都是線(xiàn)程安全的,但是并非所有的線(xiàn)程安全的代碼都是可重入的。
2、線(xiàn)程本地存儲(chǔ)
volatile關(guān)鍵字的作用
1、保證此變量對(duì)所有線(xiàn)程的可見(jiàn)性,可見(jiàn)性指當(dāng)一條線(xiàn)程修改了這個(gè)變量的值,新值對(duì)于其他線(xiàn)程來(lái)說(shuō)是可以立即得到的。
深入理解Java線(xiàn)程池
如果并發(fā)的線(xiàn)程數(shù)量很多,并且每個(gè)線(xiàn)程都是執(zhí)行一個(gè)時(shí)間很短的任務(wù)就結(jié)束了,這樣頻繁創(chuàng)建線(xiàn)程就會(huì)大大降低系統(tǒng)的效率,因?yàn)轭l繁創(chuàng)建線(xiàn)程和銷(xiāo)毀線(xiàn)程需要時(shí)間。
Java中通過(guò)線(xiàn)程池來(lái)達(dá)到使線(xiàn)程可以復(fù)用,就是執(zhí)行完一個(gè)任務(wù),并不被銷(xiāo)毀,而是可以繼續(xù)執(zhí)行其他的任務(wù),核心ThreadPoolExecutor
ThreadPoolExecutor類(lèi)中有幾個(gè)非常重要的方法:execute()、submit()、shutdown()、shutdownNow()。
execute()是核心方法。本是Executor中聲明的方法,通過(guò)該方法可以向線(xiàn)程池提交一個(gè)任務(wù),交由線(xiàn)程池去執(zhí)行。
corePoolSize:核心池大小(即線(xiàn)程池中的線(xiàn)程數(shù)目大于這個(gè)參數(shù)時(shí),提交的任務(wù)會(huì)被放進(jìn)任務(wù)緩存隊(duì)列)。
maximumPoolSize:線(xiàn)程池最大能容納的線(xiàn)程數(shù)。
任務(wù)提交給線(xiàn)程池之后的處理策略:
1)如果當(dāng)前線(xiàn)程池中的線(xiàn)程數(shù)目小于corePoolSize,則每來(lái)一個(gè)任務(wù),就會(huì)創(chuàng)建一個(gè)線(xiàn)程去執(zhí)行這個(gè)任務(wù);
2)如果當(dāng)前線(xiàn)程池中的線(xiàn)程數(shù)目>=corePoolSize,則每來(lái)一個(gè)任務(wù),會(huì)嘗試將其添加到任務(wù)緩存隊(duì)列當(dāng)中,若添加成功,則該任務(wù)會(huì)等待空閑線(xiàn)程將其取出去執(zhí)行;若添加失?。ㄒ话銇?lái)說(shuō)就是任務(wù)緩存隊(duì)列已滿(mǎn)),則會(huì)嘗試創(chuàng)建新的線(xiàn)程去執(zhí)行這個(gè)任務(wù)。
3)如果當(dāng)前線(xiàn)程中的線(xiàn)程數(shù)目達(dá)到maximumPoolSize,則會(huì)采取任務(wù)拒絕策略進(jìn)行處理。
4)如果線(xiàn)程池中的線(xiàn)程數(shù)量大于corePoolSize時(shí),如果某線(xiàn)程空閑時(shí)間超過(guò)keepAliveTime,線(xiàn)程將被終止,直至線(xiàn)程池中的線(xiàn)程數(shù)目不大于corePoolSize,如果允許為核心池中的線(xiàn)程設(shè)置存活時(shí)間,那么核心池中的線(xiàn)程空閑時(shí)間超過(guò)keepAliveTime,線(xiàn)程池也會(huì)被終止。
實(shí)現(xiàn)多線(xiàn)程有兩種實(shí)現(xiàn)方法:一種是繼承Thread類(lèi),另一種是實(shí)現(xiàn)Runnable接口。
實(shí)現(xiàn)Runnable接口相比繼承Thread類(lèi)有如下優(yōu)勢(shì):
- 可以避免由于Java的單繼承特性而帶來(lái)的局限;
- 增強(qiáng)程序的健壯性,代碼能夠被多個(gè)程序共享,代碼與數(shù)據(jù)是獨(dú)立的;
- 適合多個(gè)相同程序代碼的線(xiàn)程區(qū)處理同一資源的情況。
Java5之后出現(xiàn)第三種方式:實(shí)現(xiàn)Callable接口。
線(xiàn)程中斷
當(dāng)一個(gè)線(xiàn)程運(yùn)行時(shí),另一線(xiàn)程可以調(diào)用對(duì)應(yīng)的Thread對(duì)象的interrupt()方法來(lái)中斷它,該方法只是在目標(biāo)線(xiàn)程中設(shè)置一個(gè)標(biāo)志,表示它已經(jīng)被中斷,并立即返回。
yield和join方法的使用
1)join方法用線(xiàn)程對(duì)象調(diào)用,如果在一個(gè)線(xiàn)程A中調(diào)用另一個(gè)線(xiàn)程B的join方法,線(xiàn)程A將會(huì)等待線(xiàn)程B執(zhí)行完畢后再執(zhí)行。
2)yield方法可以直接用Thread類(lèi)調(diào)用,yield讓出CPU執(zhí)行權(quán)給同等級(jí)的線(xiàn)程,如果沒(méi)有相同級(jí)別的線(xiàn)程在等待CPU的執(zhí)行權(quán),則該線(xiàn)程繼續(xù)執(zhí)行。
兩類(lèi)線(xiàn)程
Java中有兩類(lèi)線(xiàn)程:User Thread(用戶(hù)線(xiàn)程)和Daemon Thread(守護(hù)線(xiàn)程)
用戶(hù)線(xiàn)程即運(yùn)行在前臺(tái)的線(xiàn)程,而守護(hù)線(xiàn)程是運(yùn)行在后臺(tái)的線(xiàn)程。
守護(hù)線(xiàn)程作用是為其他前臺(tái)線(xiàn)程的運(yùn)行提供便利服務(wù),而且僅在普通、非守護(hù)線(xiàn)程仍然運(yùn)行時(shí)才需要。垃圾回收線(xiàn)程就是一個(gè)守護(hù)線(xiàn)程。
可以用Thread的setDaemon(true)方法設(shè)置當(dāng)前線(xiàn)程為守護(hù)線(xiàn)程。
不要在守護(hù)線(xiàn)程中執(zhí)行業(yè)務(wù)邏輯操作(比如對(duì)數(shù)據(jù)的讀寫(xiě)等)。
1、setDaemon(true)必須在調(diào)用線(xiàn)程的start()方法之前設(shè)置;
2、在守護(hù)線(xiàn)程中產(chǎn)生的新線(xiàn)程也是守護(hù)線(xiàn)程;
3、不要認(rèn)為所有的應(yīng)用都可以分配給守護(hù)線(xiàn)程來(lái)進(jìn)行服務(wù),比如讀寫(xiě)操作或者計(jì)算邏輯。
線(xiàn)程阻塞
線(xiàn)程可以阻塞于四種狀態(tài):
1、當(dāng)線(xiàn)程執(zhí)行Thread.sleep()時(shí),它一直阻塞到指定的毫秒時(shí)間之后,或者阻塞被另一個(gè)線(xiàn)程打斷;
2、當(dāng)線(xiàn)程碰到一條wait()語(yǔ)句時(shí),它會(huì)一直阻塞到接到通知(notify)、被中斷或經(jīng)過(guò)了指定毫秒時(shí)間為止(若指定了超時(shí)值的話(huà))。
3、線(xiàn)程阻塞與不同的I/O的方式有多種,常見(jiàn)的一種是InputStream的read()方法,該方法一直阻塞到從流中讀取一個(gè)字節(jié)的數(shù)據(jù)為止,它可以無(wú)限阻塞,因此不能指定超時(shí)時(shí)間。
4、線(xiàn)程池也可以阻塞等待獲取某個(gè)對(duì)象鎖的排他性訪(fǎng)問(wèn)權(quán)限(即等待獲得synchronized語(yǔ)句必須的鎖時(shí)阻塞)。
并非所有的阻塞狀態(tài)都是可中斷的,以上阻塞狀態(tài)的前兩種可以被中斷,后兩種不會(huì)對(duì)中斷做出反應(yīng)。
在Collections類(lèi)中有多個(gè)靜態(tài)方法,它們可以獲取通過(guò)同步方法封裝非同步集合而得到的集合:
public static Collection synchronizedCollection(Collection c);
public static List synchronizedList(List l);
public static Map synchronizedMap(Map m);
public static Set synchronizedSet(Set s);
public static SortedMap synchronizedSortedMap(SortedMap sm);
public static SortedSet synchronizedSortedSet(SortedSet ss);
死鎖
當(dāng)線(xiàn)程需要同時(shí)持有多個(gè)鎖時(shí),有可能產(chǎn)生死鎖。
線(xiàn)程A當(dāng)前持有互斥鎖lock1,線(xiàn)程B當(dāng)前持有互斥鎖lock2。當(dāng)線(xiàn)程A仍然持有l(wèi)ock1時(shí),它試圖獲取lock2,因?yàn)榫€(xiàn)程B正持有l(wèi)ock2,因此線(xiàn)程A會(huì)阻塞等待線(xiàn)程B對(duì)lock2的釋放。如果此時(shí)線(xiàn)程B在持有l(wèi)ock2的時(shí)候,也在試圖獲取lock1,因?yàn)榫€(xiàn)程A正持有l(wèi)ock1,因此線(xiàn)程B會(huì)阻塞等待A對(duì)lock1的釋放。二者都在等待對(duì)方所持有鎖的釋放,而二者卻又都沒(méi)釋放自己所持有的鎖,這時(shí)二者便會(huì)一直阻塞下去,這種情況稱(chēng)為死鎖。
避免死鎖是一件困難的事,遵循以下原則有助于規(guī)避死鎖:
1、只在必要的最短時(shí)間內(nèi)持有鎖,考慮使用同步語(yǔ)句代替整個(gè)同步方法;
2、盡量編寫(xiě)不在同一時(shí)刻需要持有多個(gè)鎖的代碼,如果不可避免,則確保線(xiàn)程持有第二個(gè)鎖的時(shí)間盡量短暫;
3、創(chuàng)建和使用一個(gè)大鎖來(lái)代替若干小鎖,并把這個(gè)鎖用于互斥,而不是用作單個(gè)對(duì)象的對(duì)象級(jí)別鎖。
可重入內(nèi)置鎖
每個(gè)Java對(duì)象都可以用作一個(gè)實(shí)現(xiàn)同步的鎖,這些鎖被稱(chēng)為內(nèi)置鎖或監(jiān)視器鎖。獲得內(nèi)置鎖的唯一途徑進(jìn)入由這個(gè)鎖保護(hù)的同步代碼塊或方法。
Java NIO(New IO)
Java NIO是一個(gè)可以替代標(biāo)準(zhǔn)Java IO API的IO API。
標(biāo)準(zhǔn)的IO基于字節(jié)流和字符流進(jìn)行操作,而NIO是基于通道(Channel)和緩沖區(qū)(Buffer)進(jìn)行操作,數(shù)據(jù)總是從通道讀取到緩沖區(qū)中,或者從緩沖區(qū)寫(xiě)入通道也類(lèi)似。
Java NIO可以讓你非阻塞的使用IO,Java NIO引入了選擇器的概念,選擇器用于監(jiān)聽(tīng)多個(gè)通道的事件。
NIO由以下核心部分組成:Channels、Buffers、Selectors
Selector允許單線(xiàn)程處理多個(gè)Channel,如果你的應(yīng)用打開(kāi)了多個(gè)連接(通道),但每一個(gè)連接的流量都很低,使用Selector就會(huì)很方便。
要使用Selector,得向Selector注冊(cè)Channel,然后調(diào)用它的select()方法,這個(gè)方法會(huì)一直阻塞到某個(gè)注冊(cè)的通道有事件就緒。
Java NIO的通道類(lèi)似流,但又有些不同:
1)既可以從通道中讀取數(shù)據(jù),又可以寫(xiě)數(shù)據(jù)到通道,但流的讀寫(xiě)通常是單向的;
2)通道可以異步的讀寫(xiě);
3)通道的數(shù)據(jù)總是要先讀到一個(gè)Buffer,或者總要從一個(gè)Buffer中寫(xiě)入。
FileChannel從文件中讀取數(shù)據(jù)
DataChannel能通過(guò)UDP讀寫(xiě)網(wǎng)絡(luò)中的數(shù)據(jù)
SocketChannel能通過(guò)TCP讀寫(xiě)網(wǎng)絡(luò)中的數(shù)據(jù)
ServerSocketChannel可以監(jiān)聽(tīng)新進(jìn)來(lái)的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來(lái)的連接都會(huì)創(chuàng)建一個(gè)SocketChannel。
Buffer:緩沖區(qū)本質(zhì)是一塊可以寫(xiě)入數(shù)據(jù),然后可以從中讀取數(shù)據(jù)的內(nèi)存。
這塊內(nèi)存被包裝成NIO Buffer對(duì)象,并提供了一組方法,用來(lái)方便地訪(fǎng)問(wèn)這塊內(nèi)存。使用Buffer讀寫(xiě)數(shù)據(jù)四個(gè)步驟:
1、寫(xiě)入數(shù)據(jù)到Buffer;
2、調(diào)用flip()方法;
3、從Buffer中讀取數(shù)據(jù);
4、調(diào)用clear()方法或者compact()方法。