java多線程編程基礎

一、進程和線程的區別:

  進程:每個進程都有獨立的代碼和數據空間(進程上下文),進程間的切換會有較大的開銷,一個進程包含1--n個線程。(進程是資源分配的最小單位)
  線程:同一類線程共享代碼和數據空間,每個線程有獨立的運行棧和程序計數器(PC),線程切換開銷小。(線程是cpu調度的最小單位)線程和進程一樣分爲五個階段:創建、就緒、運行、阻塞、終止。

  多進程是指操作系統能同時運行多個任務(程序)。
  多線程是指在同一程序中有多個順序流在執行。

在java中要想實現多線程,有兩種手段,一種是繼續Thread類,另外一種是實現Runable接口.

二、Thread和Runnable的區別:
如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable接口的話,則很容易的實現資源共享。
實現Runnable接口比繼承Thread類所具有的優勢:
1):適合多個相同的程序代碼的線程去處理同一個資源
2):可以避免java中的單繼承的限制
3):增加程序的健壯性,代碼可以被多個線程共享,代碼和數據獨立

4):線程池只能放入實現Runable或callable類線程,不能直接放入繼承Thread的類

在java中,每次程序運行至少啓動2個線程。一個是main線程,一個是垃圾收集線程。因爲每當使用java命令執行一個類的時候,實際上都會啓動一個JVM,每一個JVM實際上就是在操作系統中啓動了一個進程。

三、線程狀態轉換:


1、NEW(新建)
創建後尚未啓動的線程處於這個狀態。
意思是這個線程沒有被start()啓動,或者說還根本不是一個真正意義上的線程,從本質上講這只是創建了一個Java外殼,還沒有真正的線程來運行。
不代表調用了start(),狀態就立即改變,中間還有一些步驟,如果在這個啓動的過程中有另一個線程來獲取它的狀態,其實是不確定的,要看那些中間步驟是否已經完成了。


2、RUNNABLE(可運行)
RUNNABLE狀態包括了操作系統線程狀態中的Running和Ready,也就是處於此狀態的線程可能正在運行,也可能正在等待系統資源,如等待CPU爲它分配時間片,如等待網絡IO讀取數據。
RUNNABLE狀態也可以理解爲存活着正在嘗試徵用CPU的線程(有可能這個瞬間並沒有佔用CPU,但是它可能正在發送指令等待系統調度)。由於在真正的系統中,並不是開啓一個線程後,CPU就只爲這一個線程服務,它必須使用許多調度算法來達到某種平衡,不過這個時候線程依然處於RUNNABLE狀態。

3、BLOCKED(阻塞)
BLOCKED稱爲阻塞狀態,或者說線程已經被掛起,它“睡着”了,原因通常是它在等待一個“鎖”,當嘗試進入一個synchronized語句塊/方法時,鎖已經被其它線程佔有,就會被阻塞,直到另一個線程走完臨界區或發生了相應鎖對象的wait()操作後,它纔有機會去爭奪進入臨界區的權利
在Java代碼中,需要考慮synchronized的粒度問題,否則一個線程長時間佔用鎖,其它爭搶鎖的線程會一直阻塞,直到擁有鎖的線程釋放鎖
處於BLOCKED狀態的線程,即使對其調用 thread.interrupt()也無法改變其阻塞狀態,因爲interrupt()方法只是設置線程的中斷狀態,即做一個標記,不能喚醒處於阻塞狀態的線程

注意:ReentrantLock.lock()操作後進入的是WAITING狀態,其內部調用的是LockSupport.park()方法

4、WAITING(無限期等待)
處於這種狀態的線程不會被分配CPU執行時間,它們要等待顯示的被其它線程喚醒。這種狀態通常是指一個線程擁有對象鎖後進入到相應的代碼區域後,調用相應的“鎖對象”的wait()方法操作後產生的一種結果。變相的實現還有LockSupport.park()、Thread.join()等,它們也是在等待另一個事件的發生,也就是描述了等待的意思。

以下方法會讓線程陷入無限期等待狀態:
(1)沒有設置timeout參數的Object.wait()
(2)沒有設置timeout參數的Thread.join()
(3)LockSupport.park()

注意:
LockSupport.park(Object blocker) 會掛起當前線程,參數blocker是用於設置當前線程的“volatile Object parkBlocker 成員變量”
parkBlocker 是用於記錄線程是被誰阻塞的,可以通過LockSupport.getBlocker()獲取到阻塞的對象,用於監控和分析線程用的。

“阻塞”與“等待”的區別:
(1)“阻塞”狀態是等待着獲取到一個排他鎖,進入“阻塞”狀態都是被動的,離開“阻塞”狀態是因爲其它線程釋放了鎖,不阻塞了;
(2)“等待”狀態是在等待一段時間 或者 喚醒動作的發生,進入“等待”狀態是主動的
如主動調用Object.wait(),如無法獲取到ReentraantLock,主動調用LockSupport.park(),如主線程主動調用 subThread.join(),讓主線程等待子線程執行完畢再執行
離開“等待”狀態是因爲其它線程發生了喚醒動作或者到達了等待時間

5、TIMED_WAITING(限期等待)
處於這種狀態的線程也不會被分配CPU執行時間,不過無需等待被其它線程顯示的喚醒,在一定時間之後它們會由系統自動的喚醒。
以下方法會讓線程進入TIMED_WAITING限期等待狀態:
(1)Thread.sleep()方法
(2)設置了timeout參數的Object.wait()方法
(3)設置了timeout參數的Thread.join()方法
(4)LockSupport.parkNanos()方法
(5)LockSupport.parkUntil()方法

6、TERMINATED(終止)

已終止線程的線程狀態,線程已經結束執行。換句話說,run()方法走完了,線程就處於這種狀態。其實這只是Java語言級別的一種狀態,在操作系統內部可能已經註銷了相應的線程,或者將它複用給其他需要使用線程的請求,而在Java語言級別只是通過Java代碼看到的線程狀態而已。


四、常用函數說明:

①sleep(long millis): 線程睡眠。

使線程轉到阻塞狀態。millis參數設定睡眠的時間,以毫秒爲單位。當睡眠結束後,就轉爲就緒(Runnable)狀態。

②join():線程加入。
join是Thread類的一個方法,啓動線程後直接調用,即join()的作用是:“等待該線程終止”,這裏需要理解的就是該線程是指的主線程等待子線程的終止。也就是在子線程調用了join()方法後面的代碼,只有等到子線程結束了才能執行。如果有多個子線程都調用了join()方法,那麼主線程需要等待多個子線程結束才能繼續執行join()方法後的代碼。

③yield():線程讓步。暫停當前正在執行的線程對象,把執行機會讓給相同或者更高優先級的線程。
yield()應該做的是讓當前運行線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行機會。因此,使用yield()的目的是讓相同優先級的線程之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因爲讓步的線程還有可能被線程調度程序再次選中。
結論:yield()從未導致線程轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但有可能沒有效果。

sleep()和yield()的區別:
sleep()和yield()的區別):sleep()使當前線程進入停滯狀態,所以執行sleep()的線程在指定的時間內肯定不會被執行;yield()只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行。
sleep 方法使當前運行中的線程睡眼一段時間,進入不可運行狀態,這段時間的長短是由程序設定的,yield 方法使當前線程讓出 CPU 佔有權,但讓出的時間是不可設定的。實際上,yield()方法對應瞭如下操作:先檢測當前是否有相同優先級的線程處於同可運行狀態,如有,則把 CPU  的佔有權交給此線程,否則,繼續運行原來的線程。所以yield()方法稱爲“退讓”,它把運行機會讓給了同等優先級的其他線程
另外,sleep 方法允許較低優先級的線程獲得運行機會,但 yield()  方法執行時,當前線程仍處在可運行狀態,所以,不可能讓出較低優先級的線程些時獲得 CPU 佔有權。在一個運行系統中,如果較高優先級的線程沒有調用 sleep 方法,又沒有受到 I\O 阻塞,那麼,較低優先級線程只能等待所有較高優先級的線程運行結束,纔有機會運行。 

④setPriority(): 更改線程的優先級。

⑤interrupt():不要以爲它是中斷某個線程!它只是線線程發送一箇中斷信號,讓線程在無限等待時(如死鎖時)能拋出異常,從而結束線程,但是如果你吃掉了這個異常,那麼這個線程還是不會中斷的!

⑥wait()
Obj.wait(),與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內。從功能上來說wait就是說線程在獲取對象鎖後,主動釋放對象鎖,同時本線程休眠。直到有其它線程調用對象的notify()喚醒該線程,才能繼續獲取對象鎖,並繼續執行。相應的notify()就是對對象鎖的喚醒操作。但有一點需要注意的是notify()調用後,並不是馬上就釋放對象鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()對象鎖的線程中隨機選取一線程,賦予其對象鎖,喚醒線程,繼續執行。這樣就提供了在線程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前線程,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了對象鎖的控制。

wait和sleep區別:
共同點:
 
1. 他們都是在多線程的環境下,都可以在程序的調用處阻塞指定的毫秒數,並返回。 
2. wait()和sleep()都可以通過interrupt()方法 打斷線程的暫停狀態 ,從而使線程立刻拋出InterruptedException。 
   如果線程A希望立即結束線程B,則可以對線程B對應的Thread實例調用interrupt方法。如果此刻線程B正在wait/sleep /join,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結束線程。 
   需要注意的是,InterruptedException是線程自己從內部拋出的,並不是interrupt()方法拋出的。對某一線程調用 interrupt()時,如果該線程正在執行普通的代碼,那麼該線程根本就不會拋出InterruptedException。但是,一旦該線程進入到 wait()/sleep()/join()後,就會立刻拋出InterruptedException 。 
不同點: 
1. Thread類的方法:sleep(),yield()等 
   Object的方法:wait()和notify()等 
2. 每個對象都有一個鎖來控制同步訪問。synchronized關鍵字可以和對象的鎖交互,來實現線程的同步。 
   sleep方法沒有釋放鎖,而wait方法釋放了鎖,使得其他線程可以使用同步控制塊或者方法。 
3. wait,notify和notifyAll只能在同步控制方法或者同步控制塊裏面使用,而sleep可以在任何地方使用 
所以sleep()和wait()方法的最大區別是:
sleep()睡眠時,保持對象鎖,仍然佔有該鎖;而wait()睡眠時,釋放對象鎖。
但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態,從而使線程立刻拋出InterruptedException(但不建議使用該方法)。

sleep()方法
sleep()使當前線程進入停滯狀態(阻塞當前線程),讓出CUP的使用、目的是不讓當前線程獨自霸佔該進程所獲的CPU資源,以留一定時間給其他線程執行的機會;
sleep()是Thread類的Static(靜態)的方法;因此他不能改變對象的鎖,所以當在一個synchronized塊中調用sleep()方法時,線程雖然休眠了,但是對象的鎖並木有被釋放,其他線程無法訪問這個對象(即使睡着也持有對象鎖)。
在sleep()休眠時間期滿後,該線程不一定會立即執行,這是因爲其它線程可能正在運行而且沒有被調度爲放棄執行,除非此線程具有更高的優先級。 
wait()方法
wait()方法是Object類裏的方法;當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時失去(釋放)了對象的鎖(暫時失去鎖,wait(long timeout)超時時間到後還需要返還對象鎖);其他線程可以訪問;
wait()使用notify或者notifyAll或者指定睡眠時間來喚醒當前等待池中的線程。
wiat()必須放在synchronized block中,否則會在program runtime時扔出”java.lang.IllegalMonitorStateException“異常。


五、synchronized關鍵字:

1、synchronized關鍵字的作用域有二種: 
1)是某個對象實例內,synchronized aMethod(){}可以防止多個線程同時訪問這個對象的synchronized方法(如果一個對象有多個synchronized方法,只要一個線程訪問了其中的一個synchronized方法,其它線程不能同時訪問這個對象中任何一個synchronized方法)。這時,不同的對象實例的synchronized方法是不相干擾的。也就是說,其它線程照樣可以同時訪問相同類的另一個對象實例中的synchronized方法; 
2)是某個類的範圍,synchronized static aStaticMethod{}防止多個線程同時訪問這個類中的synchronized static 方法。它可以對類的所有對象實例起作用。 

2、除了方法前用synchronized關鍵字,synchronized關鍵字還可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。用法是: synchronized(this){/*區塊*/},它的作用域是當前對象; 或者synchronized(class){/*區塊*/},它的作用域是這個類的所有實例對象;

3、synchronized關鍵字是不能繼承的,也就是說,基類的方法synchronized f(){} 在繼承類中並不自動是synchronized f(){},而是變成了f(){}。繼承類需要你顯式的指定它的某個方法爲synchronized方法;




發佈了70 篇原創文章 · 獲贊 28 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章