本篇文章給大家分享的是有關(guān)怎么進行Java多線程概念和使用原理的分析,小編覺得挺實用的,因此分享給大家學習,希望大家閱讀完這篇文章后可以有所收獲,話不多說,跟著小編一起來看看吧。
成都創(chuàng)新互聯(lián)的客戶來自各行各業(yè),為了共同目標,我們在工作上密切配合,從創(chuàng)業(yè)型小企業(yè)到企事業(yè)單位,感謝他們對我們的要求,感謝他們從不同領(lǐng)域給我們帶來的挑戰(zhàn),讓我們激情的團隊有機會用頭腦與智慧不斷的給客戶帶來驚喜。專業(yè)領(lǐng)域包括成都網(wǎng)站建設(shè)、成都網(wǎng)站設(shè)計、電商網(wǎng)站開發(fā)、微信營銷、系統(tǒng)平臺開發(fā)。
首選了解一下什么是進程和線程的概念?
進程是運行中的應(yīng)用程序,每個進程都有自己獨立的一塊內(nèi)存空間,一個進程可以啟動多個線程,而線程是指進程中的一個執(zhí)行流程,也可以理解成一段代碼。比如java.exe進程中可以運行很多線程,線程永遠屬于某個進程,進程中的多個線程共享進程的內(nèi)存。
在Java中為了創(chuàng)建一個新的線程,必須指明線程所要執(zhí)行的代碼。通過Java提供的類java.lang.Thread來方便多線程編程,這個類提供了大量的方法來方便我們控制自己的各個線程。那么如何通過Java線程來執(zhí)行的代碼呢?
Thread類中提供了一個最重要的run()方法,它被Thread類的start()方法調(diào)用,提供給我們線程要執(zhí)行的代碼。為了執(zhí)行我們自己的代碼只需要重寫該方法即可。
方式一:繼承Thread類,重寫run()方法
創(chuàng)建Thread類的子類繼承該對象重寫run()方法,具體代碼實例如下:
上述方法簡單明了符合大家的寫作習慣,但是有一個很大的缺點,那就是如果我們的類已經(jīng)是其他類的子類,就無法再繼承Thread類,此時如果不想創(chuàng)建一個新的類,應(yīng)該怎么辦呢?
我們只能將我們的方法作為參數(shù)傳遞給Thread類的實例,類似回調(diào)函數(shù),但是Java是沒有指針的,我們只能傳遞一個包含該方法的實例。Java使用接口java.lang.Runnable解決這個問題。
Runnable接口只有一個run()方法,聲明類實現(xiàn)Runnable接口并提供這一方法,將線程代碼寫入這個方法完成任務(wù),但是Runnable接口并沒有任何對線程的支持,因此必須創(chuàng)建Thread類的實例,通過Thread類的構(gòu)造函數(shù)完成。
方式二:實現(xiàn)Runnable接口,代碼實例如下:
使用Runnable接口來實現(xiàn)多線程使得我們能夠在一個類中包容所有的代碼,有利于封裝。缺點在于只能使用一套代碼,若想創(chuàng)建多個線程并使各個線程執(zhí)行不同的代碼,則仍必須額外創(chuàng)建類,如果這樣的話,在大多數(shù)情況下也許還不如直接用多個類分別繼承Thread來得方便。綜上所述比較兩種實現(xiàn)多線程的方法各有千秋,大家可以靈活運用。
下面和大家分析一下多線程在使用中的一些常見問題。
一:線程可以劃分為七種狀態(tài)(有些人認為只有五種,將鎖池和等待池看成阻塞狀態(tài)的特殊情況,這種認知也是正確的但是鎖池和等待池單獨分離有利于對程序的理解)。
1.初始狀態(tài),線程創(chuàng)建,線程對象調(diào)用start()方法
2.可運行狀態(tài),也就是等待CPU資源,等待運行的狀態(tài)
3.運行狀態(tài),獲得了CPU資源,正在運行狀態(tài)
4.阻塞狀態(tài),也就是讓出CPU資源,進入一種等待狀態(tài),而且不是可運行狀態(tài),有三種情況會進入阻塞狀態(tài)。
1)如等待輸入(輸入設(shè)備進行處理,而CUP不處理),則放入阻塞,直到輸入完畢,阻塞結(jié)束后會進入可運行狀態(tài)。
2)線程休眠,線程對象調(diào)用sleep()方法,阻塞結(jié)束后會進入可運行狀態(tài)。
3)線程對象 2 調(diào)用線程對象 1 的join()方法,那么線程對象 2 進入阻塞狀態(tài),直到線程對象 1 中止。
5.中止狀態(tài),也就是執(zhí)行結(jié)束
6.鎖池狀態(tài)
7.等待隊列
二:線程優(yōu)先級
線程的優(yōu)先級代表該線程的重要程度,當有多個線程同時處于可執(zhí)行狀態(tài)并等待獲得CPU時間時,線程調(diào)度系統(tǒng)根據(jù)各個線程的優(yōu)先級來決定給誰分配CPU時間,優(yōu)先級高的線程有更大的機會獲得CPU時間,優(yōu)先級低的線程獲取CPU時間幾率比較小。調(diào)用Thread類的getPriority()和setPriority()方法來存取線程的優(yōu)先級,線程的優(yōu)先級界于1(MIN_PRIORITY)和10(MAX_PRIORITY)之間,缺省是5(NORM_PRIORITY)。
三:線程同步
由于同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問沖突嚴重的問題。Java語言提供了專門的機制以解決這種沖突,有效避免了同一個數(shù)據(jù)對象被多個線程同時訪問。由于我們可以通過private關(guān)鍵字來保證數(shù)據(jù)對象只能被方法訪問,所以我們只需針對方法提出一套機制,這套機制就是synchronized關(guān)鍵字,它包括兩種用法:synchronized方法和synchronized塊。
1. 通過在方法聲明中加入synchronized關(guān)鍵字來聲明synchronized方法。如:public synchronized void accessVal(int newVal);synchronized方法控制對類成員變量的訪問:每個類實例對應(yīng)一把鎖,每個synchronized方法都必須獲得調(diào)用該方法的類實例的鎖方能執(zhí)行,否則所屬線程阻塞,方法一旦執(zhí)行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此后被阻塞的線程方能獲得該鎖,重新進入可執(zhí)行狀態(tài)。
這種機制確保了同一時刻對于每一個類實例,其所有聲明為synchronized的成員函數(shù)中至多只有一個處于可執(zhí)行狀態(tài),從而有效避免了類成員變量的訪問沖突。在Java中不單單只有類實例,每一個類也是對應(yīng)一把鎖,這樣我們也可將類的靜態(tài)成員函數(shù)聲明為synchronized,以控制其對類的靜態(tài)成員變量的訪問。
synchronized方法的缺陷:若將一個大的方法聲明為synchronized將會大大影響效率,典型地,若將線程類的run()方法聲明為synchronized,由于在線程的整個生命期內(nèi)它一直在運行,因此將導(dǎo)致它對本類任何synchronized方法的調(diào)用都永遠不會成功。當然我們可以通過將訪問類成員變量的代碼放到專門的方法中將其聲明為synchronized,并在主方法中調(diào)用來解決這一問題,但是Java為我們提供了更好的解決辦法,那就是synchronized塊。
2. 通過synchronized關(guān)鍵字來聲明synchronized塊,語法如下:
synchronized(syncObject) {
//允許訪問控制的代碼
}
synchronized塊是一個代碼塊,其中的代碼必須獲得對象syncObject(類實例或類)的鎖方能執(zhí)行。由于可以針對任意代碼塊且可任意指定上鎖的對象,因此靈活性比較高。
四:線程阻塞
為了解決對共享存儲區(qū)的訪問沖突,Java引入了同步機制。阻塞指的是暫停一個線程的執(zhí)行以等待某個條件發(fā)生(如某資源就緒),Java提供了大量方法來支持阻塞,下面逐一進行分析:
1. sleep()方法:sleep()允許指定以毫秒為單位的一段時間作為參數(shù),它使得線程在指定的時間內(nèi)進入阻塞狀態(tài),不能得到CPU時間,指定的時間一過,線程重新進入可執(zhí)行狀態(tài)。
2. suspend()和resume()方法:兩個方法配合使用,suspend()使得線程進入阻塞狀態(tài)不會自動恢復(fù),必須其對應(yīng)的resume()被調(diào)用,才能使得線程重新進入可執(zhí)行狀態(tài)。
3. yield()方法:yield()使得線程放棄當前分得的CPU時間,但是不使線程阻塞,即線程仍處于可執(zhí)行狀態(tài),隨時可能再次分得CPU時間。調(diào)用yield()的效果等價于調(diào)度程序認為該線程已執(zhí)行了足夠的時間從而轉(zhuǎn)到另一個線程。
4. wait()和 notify()方法:兩個方法配合使用,wait()使得線程進入阻塞狀態(tài),它有兩種形式,一種允許指定以毫秒為單位的一段時間作為參數(shù),另一種沒有參數(shù),前者當對應(yīng)的notify()被調(diào)用或者超出指定時間時線程重新進入可執(zhí)行狀態(tài),后者則必須對應(yīng)的notify()被調(diào)用。注意的是1 2 3 步驟阻塞時都不會釋放占用的鎖而這一對方法則會釋放占用的鎖。
關(guān)于 wait()和 notify()方法最后再說明兩點:
1)調(diào)用notify()方法導(dǎo)致解除阻塞的線程是因為調(diào)用該對象的wait()方法而阻塞的線程中隨機選取的,我們無法預(yù)料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產(chǎn)生問題。
2)除了notify(),還有一個notifyAll()方法也可起到類似作用,唯一的區(qū)別在于,調(diào)用notifyAll()方法將把因為調(diào)用該對象的wait()方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執(zhí)行狀態(tài)。
說到阻塞就不能不說一說死鎖的概念,略一分析就能發(fā)現(xiàn)suspend()方法和不指定超時期限的wait()方法的調(diào)用都可能產(chǎn)生死鎖。遺憾的是Java并不在語言級別上支持死鎖的避免,因此我們在編程中必須小心地避免死鎖。
五:守護線程
守護線程是一類特殊的線程,它和普通線程的區(qū)別在于它并不是應(yīng)用程序的核心部分,當一個應(yīng)用程序的所有非守護線程終止運行時,即使仍然有守護線程在運行,應(yīng)用程序也將終止,相反只要有一個非守護線程在運行應(yīng)用程序就不會終止。守護線程一般被用于在后臺為其它線程提供服務(wù)??梢酝ㄟ^調(diào)用isDaemon()方法來判斷一個線程是否是守護線程,可以調(diào)用setDaemon()方法來將一個線程設(shè)為守護線程。
六:線程組
線程組是Java特有的一個概念,在Java中線程組是ThreadGroup類的對象,每個線程都隸屬于唯一一個線程組,這個線程組在線程創(chuàng)建時指定并在線程的整個生命期內(nèi)都不能更改。你可以通過調(diào)用包含 ThreadGroup 類型參數(shù)的 Thread 類構(gòu)造函數(shù)來指定線程屬的線程組,若沒有指定則線程缺省的隸屬于名為system的系統(tǒng)線程組。
在Java中除了預(yù)建的系統(tǒng)線程組外,所有線程組都必須顯式創(chuàng)建并且每個線程組又隸屬于另一個線程組。你可以在創(chuàng)建線程組時指定其所隸屬的線程組,若沒有指定則缺省的隸屬于系統(tǒng)線程組。這樣所有線程組組成了一棵以系統(tǒng)線程組為根的樹。
Java線程組實例代碼,具體如下:
源碼參考:http://blog.yoodb.com/yoodb/article/detail/1330
Java允許我們對一個線程組中的所有線程同時進行操作,比如我們可以通過調(diào)用線程組的相應(yīng)方法來設(shè)置其中所有線程的優(yōu)先級,也可以啟動或阻塞其中的所有線程。
Java的線程組機制的另一個重要作用是線程安全。線程組機制允許我們通過分組來區(qū)分有不同安全特性的線程,對不同組的線程進行不同的處理,還可以通過線程組的分層結(jié)構(gòu)來支持不對等安全措施的采用。Java 的 ThreadGroup 類提供了大量的方法來方便我們對線程組樹中的每一個線程組以及線程組中的每一個線程進行操作。
以上就是怎么進行Java多線程概念和使用原理的分析,小編相信有部分知識點可能是我們?nèi)粘9ぷ鲿姷交蛴玫降摹OM隳芡ㄟ^這篇文章學到更多知識。更多詳情敬請關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。