Java多線程基礎

 本文轉自:http://tomyz0223.iteye.com/blog/1001778#_Toc290464587

1. 線程和進程

1.1 進程的概念

進程是表示資源分配的基本單位,又是調度運行的基本單位。例如,用戶運行自己的程序,系統就創建一個進程,併爲它分配資 源,包括各種表格、內存空間、磁盤空間、 I / O 設備等。然後,把該進程放人進程的就緒隊列。進程調度程序選中它,爲它分配 CPU 以及其它有關資源,該進程 才真正運行。所以,進程是系統中的併發執行的單位。

 

1.2 線程的概念

線程是進程中執行運算的最小單位,亦即執行處理機調度的基本單位。如果把進程理解爲在邏輯上操作系統所完成的任務,那麼線程表示完成該任務的許多可能的子任務之一。

 

1.3 引入多線程的好處

( 1 )易於調度 。

( 2 )提高併發性。通過線程可方便有效地實現併發性。進程可創建多個線程來執行同一程序的不同部分。

( 3 )開銷少。創建線程比創建進程要快,所需開銷很少。

( 4 )利於充分發揮多處理器的功能。通過創建多線程進程(即一個進程可具有兩個或更多個線程),每個線程在一個處理器上運行,從而實現應用程序的併發性,使每個處理器都得到充分運行。

 

1.4 進程和線程的關係

( 1 )一個線程只能屬於一個進程,而一個進程可以有多個線程,但至少有一個線程。

( 2 )資源分配給進程,同一進程的所有線程共享該進程的所有資源。

( 3 )處理機分給線程,即真正在處理機上運行的是線程。

( 4 )線程在執行過程中,需要協作同步。不同進程的線程間要利用消息通信的辦法實現同步。

線程是指進程內的一個執行單元 , 也是進程內的可調度實體 .

1.5 線程和 進程的區別

(1) 調度:線程作爲調度和分配的基本單位,進程作爲擁有資源的基本單位

(2) 併發性:不僅進程之間可以併發執行,同一個進程的多個線程之間也可併發執行

(3) 擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但可以訪問隸屬於進程的資源 .

(4) 系統開銷:在創建或撤消進程時,由於系統都要爲之分配和回收資源,導致系統的開銷明顯大於創建或撤消線程時的開銷。

(5) 內存訪問:進程之間的內存是獨立的,而一個進程內的內存是多個線程共享的,這就是 Java 的併發理論都是基於解決這個問題出現的。

 

具體參見: http://apps.hi.baidu.com/share/detail/1801190

 

2. Java 內存模型

2.1 Java 內存模型的基本原理

Java 內存模型,由於 Java 被設計爲跨平臺的語言,在內存管理上,顯然也要有一個統一的 模型。系統存在一個主內存 (Main Memory) , Java 中所有變量都儲存在主存中,對於所有線程都是共享的。每條線程都有自己的工作內存 (Working Memory) ,工作內存中保存的是主存中某些變量的拷貝,線程對所有變量的操作都是在工作內存中進行,線程之間無法相互直接訪問,變量傳遞均需要通過主存完成。

 

因爲當線程處於不同的cpu中時,它各自的變量保存在各自cpu的寄存器或者高速緩存中,這樣回事的變量對於其它線程暫時不可見。

 

 

2.2 Volatile 的內存工作原理

Volatile 是保證多個線程之間變量可見性的,也就是說一個線程對變量進行了寫操作,另外一個線程能夠獲取它最新的值。

它的工作原理是,它對寫和讀都是直接操作工作主存的。(這個可以通過操作字節碼看到)

2.3 Synchronized 的內存操作模型 :

Synchronized, 有兩層語義,一個是互斥,一個是可見性。在可見性上面,它的工作原理有點不同:當線程進入同步塊時,會從主存裏面獲取最新值,然後當線程離開同步塊時,再把變量的值更新到主存。

2. 線程的同步

3.           

由於同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。 Java 語言提供了專門機制以解決這種衝突,有效避免了同一個數據對象被多個線程同時訪問。

我們只需針對方法提出一套機制,這套機制就是 synchronized 關鍵字,它包括兩種用法: synchronized 方法和synchronized 塊。

1. synchronized 方法:通過在方法聲明中加入 synchronized 關鍵字來聲明 synchronized 方法。 synchronized 方法控制對類成員變量的訪問:每個類實例對應一把鎖,每個 synchronized 方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,此後被阻塞的線程方能獲得 該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明爲synchronized 的成員函數中至多隻有一個處於可執行狀態(因爲至多隻有一個能夠獲得該類實例對應的鎖),從而有效避免了類成員變量的訪問衝突(只要所有可能訪問類成員變 量的方法均被聲明爲 synchronized )。

在 Java 中,不光是類實例,每一個類也對應一把鎖,這樣我們也可將類的靜態成員函數聲明爲 synchronized ,以控制其對類的靜態成員變量的訪問。

synchronized 方法的缺陷:若將一個大的方法聲明爲 synchronized 將會大大影響效率,典型地,若將線程類的方法 run() 聲明爲 synchronized ,由於在線程的整個生命期內它一直在運行,因此將導致它對本類任何synchronized 方法的調用都永遠不會成功。

 

2. synchronized 塊:通過 synchronized 關鍵字來聲明 synchronized 塊。語法如下:

synchronized(syncObject) {

// 允許訪問控制的代碼

}

synchronized 塊是這樣一個代碼塊,其中的代碼必須獲得對象 syncObject 的鎖方能執行,具體機制同前所述。由於可以針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。

 

4. 線程的阻塞

4.1 線程阻塞基本概念

爲了解決對共享存儲區的訪問衝突, Java 引入了同步機制,現在讓我們來考察多個線程對共享資源的訪問,顯然同步機制已經不夠了,因爲在任意時刻所要求的資源不一定已經準備好了被訪問,反過來,同 一時刻準備好了的資源也可能不止一個。爲了解決這種情況下的訪問控制問題, Java 引入了對阻塞機制的支持。

阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒)。

進入阻塞狀態可能由於下列原因:

1.       調用了 sleep 方法使線程進入休眠狀態

2.       調用了 wait 方法,使得線程掛起,直到線程調用 notify 方法

3.       用戶等待輸入

4.       網絡 IO

 

4.2 Thread.sleep()

Java 提供了大量方法來支持阻塞,下面讓對它們逐一分析。

sleep() 方法: sleep() 允許指定以毫秒爲單位的一段時間作爲參數,它使得線程在指定的時間內進入阻塞狀態,不能得到 CPU 時間,指定的時間一過,線程重新進入可執行狀態。

典型地, sleep() 被用在等待某個資源就緒的情形:測試發現條件不滿足後,讓線程阻塞一段時間後重新測試,直到條件滿足爲止。

4.2 Thread.yield()

yield() 方法: yield() 使得線程放棄當前分得的 CPU 時間,但是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另一個線程。

4.3 wait() and notify()

wait() 和 notify() 方法:兩個方法配套使用, wait() 使得線程進入阻塞狀態,它有兩種形式,一種允許指定以毫秒爲單位的一段時間作爲參數,另一種沒有參數,前者當對應的 notify() 被調用或者超出指定時間時線程重新進入可執行狀態,後者則必須對應的 notify() 被調用。

   2 和 4 區別的核心在於,前面敘述的所有方法,阻塞時都不會釋放佔用的鎖(如果佔用了的話),而這一對方法則相反。上述的核心區別導致了一系列的細節上的區別。

  首先,前面敘述的所有方法都隸屬於 Thread 類,但是這一對卻直接隸屬於 Object 類,也就是說,所有對象都擁有這一對方法。因爲這一對方法阻塞時要釋放佔用的鎖,而鎖是任何對象都具有的,調用任意對象的 wait() 方法導致線程阻塞,並且該對象上的鎖被釋放。而調用任意對象的 notify() 方法則導致因調用該對象的 wait() 方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖後才真正可執行)。

  其次,前面敘述的所有方法都可在任何位置調用,但是這一對方法卻必須在 synchronized 方法或塊中調用,理由也很簡單,只有在 synchronized 方法或塊中當前線程才佔有鎖,纔有鎖可以釋放。同樣的道理,調用這一對方法的對象上的鎖必須爲當前線程所擁有,這樣纔有鎖可以釋放。因此,這一對方法調用 必須放置在這樣的synchronized 方法或塊中,該方法或塊的上鎖對象就是調用這一對方法的對象。若不滿足這一條件,則程序雖然仍能編譯,但在運行時會出現 IllegalMonitorStateException 異常。

   wait() 和 notify() 方法的上述特性決定了它們經常和 synchronized 方法或塊一起使用,將它們和操作系統的進程間通信機制作一個比較就會發現它們的相似性: synchronized 方法或塊提供了類似於操作系統原語的功能,它們的結合用於解決各種複雜的線程間通信問題。

關於 wait() 和 notify() 方法最後再說明兩點:

  第一:調用 notify() 方法導致解除阻塞的線程是從因調用該對象的 wait() 方法而阻塞的線程中隨機選取的,我們無法預料哪一個線程將會被選擇,所以編程時要特別小心,避免因這種不確定性而產生問題。

  第二:除了 notify() ,還有一個方法 notifyAll() 也可起到類似作用,唯一的區別在於,調用 notifyAll() 方法將把因調用該對象的 wait() 方法而阻塞的所有線程一次性全部解除阻塞。當然,只有獲得鎖的那一個線程才能進入可執行狀態。

  談到阻塞,就不能不談一談死鎖,略一分析就能發現, suspend() 方法和不指定超時期限的 wait() 方法的調用都可能產生死鎖。遺憾的是, Java 並不在語言級別上支持死鎖的避免,我們在編程中必須小心地避免死鎖。

4.4 join ()

阻塞當前線程,直到指定線程執行完畢,再繼續執行

5. 線程的生命週期


6 . 守護 線程

守護線程是一類特殊的線程,它和普通線程的區別在於它並不是應用程序的核心部分,當一個 應用程序的所有非守護線程終止運行時,即使仍然有守護線程在運行,應用程序也將終止,反之,只要有一個非守護線程在運行,應用程序就不會終止。守護線程一 般被用於在後臺爲其它線程提供服務。調用方法 isDaemon() 來判斷一個線程是否是守護線程,也可以調用方法 setDaemon() 將一個線程設爲守護線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章