2016-07-18 15:40:51編程
Java 多線程基礎緩存
1.1 進程的概念網絡
進程是表示資源分配的基本單位,又是調度運行的基本單位。例如,用戶運行本身的程序,系統就建立一個進程,併爲它分配資 源,包括各類表格、內存空間、磁盤空間、 I / O 設備等。而後,把該進程放人進程的就緒隊列。進程調度程序選中它,爲它分配 CPU 以及其它有關資源,該進程 才真正運行。因此,進程是系統中的併發執行的單位。多線程
1.2 線程的概念併發
線程是進程中執行運算的最小單位,亦即執行處理機調度的基本單位。若是把進程理解爲在邏輯上操做系統所完成的任務,那麼線程表示完成該任務的許多可能的子任務之一。函數
1.3 引入多線程的好處高併發
( 1 )易於調度 。測試
( 2 )提升併發性。經過線程可方便有效地實現併發性。進程可建立多個線程來執行同一程序的不一樣部分。spa
( 3 )開銷少。建立線程比建立進程要快,所需開銷不多。操作系統
( 4 )利於充分發揮多處理器的功能。經過建立多線程進程(即一個進程可具備兩個或更多個線程),每一個線程在一個處理器上運行,從而實現應用程序的併發性,使每一個處理器都獲得充分運行。
1.4 進程和線程的關係
( 1 )一個線程只能屬於一個進程,而一個進程能夠有多個線程,但至少有一個線程。
( 2 )資源分配給進程,同一進程的全部線程共享該進程的全部資源。
( 3 )處理機分給線程,即真正在處理機上運行的是線程。
( 4 )線程在執行過程當中,須要協做同步。不一樣進程的線程間要利用消息通訊的辦法實現同步。
線程是指進程內的一個執行單元 , 也是進程內的可調度實體 .
1.5 線程和 進程的區別
(1) 調度:線程做爲調度和分配的基本單位,進程做爲擁有資源的基本單位
(2) 併發性:不只進程之間能夠併發執行,同一個進程的多個線程之間也可併發執行
(3) 擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但能夠訪問隸屬於進程的資源 .
(4) 系統開銷:在建立或撤消進程時,因爲系統都要爲之分配和回收資源,致使系統的開銷明顯大於建立或撤消線程時的開銷。
(5) 內存訪問:進程之間的內存是獨立的,而一個進程內的內存是多個線程共享的,這就是 Java 的併發理論都是基於解決這個問題出現的。
Java 內存模型,因爲 Java 被設計爲跨平臺的語言,在內存管理上,顯然也要有一個統一的 模型。系統存在一個主內存 (Main Memory) , Java 中全部變量都儲存在主存中,對於全部線程都是共享的。每條線程都有本身的工做內存 (Working Memory) ,工做內存中保存的是主存中某些變量的拷貝,線程對全部變量的操做都是在工做內存中進行,線程之間沒法相互直接訪問,變量傳遞均須要經過主存完成。
由於當線程處於不一樣的cpu中時,它各自的變量保存在各自cpu的寄存器或者高速緩存中,這樣回事的變量對於其它線程暫時不可見。
Volatile 是保證多個線程之間變量可見性的,也就是說一個線程對變量進行了寫操做,另一個線程可以獲取它最新的值。
它的工做原理是,它對寫和讀都是直接操做工做主存的。(這個能夠經過操做字節碼看到)
Synchronized, 有兩層語義,一個是互斥,一個是可見性。在可見性上面,它的工做原理有點不一樣:當線程進入同步塊時,會從主存裏面獲取最新值,而後當線程離開同步塊時,再把變量的值更新到主存。
因爲同一進程的多個線程共享同一片存儲空間,在帶來方便的同時,也帶來了訪問衝突這個嚴重的問題。 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 的鎖方能執行,具體機制同前所述。因爲能夠針對任意代碼塊,且可任意指定上鎖的對象,故靈活性較高。
爲了解決對共享存儲區的訪問衝突, Java 引入了同步機制,如今讓咱們來考察多個線程對共享資源的訪問,顯然同步機制已經不夠了,由於在任意時刻所要求的資源不必定已經準備好了被訪問,反過來,同 一時刻準備好了的資源也可能不止一個。爲了解決這種狀況下的訪問控制問題, Java 引入了對阻塞機制的支持。
阻塞指的是暫停一個線程的執行以等待某個條件發生(如某資源就緒)。
進入阻塞狀態可能因爲下列緣由:
1. 調用了 sleep 方法使線程進入休眠狀態
2. 調用了 wait 方法,使得線程掛起,直到線程調用 notify 方法
3. 用戶等待輸入
4. 網絡 IO
Java 提供了大量方法來支持阻塞,下面讓對它們逐一分析。
sleep() 方法: sleep() 容許指定以毫秒爲單位的一段時間做爲參數,它使得線程在指定的時間內進入阻塞狀態,不能獲得 CPU 時間,指定的時間一過,線程從新進入可執行狀態。
典型地, sleep() 被用在等待某個資源就緒的情形:測試發現條件不知足後,讓線程阻塞一段時間後從新測試,直到條件知足爲止。
yield() 方法: yield() 使得線程放棄當前分得的 CPU 時間,可是不使線程阻塞,即線程仍處於可執行狀態,隨時可能再次分得 CPU 時間。調用 yield() 的效果等價於調度程序認爲該線程已執行了足夠的時間從而轉到另外一個線程。
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 並不在語言級別上支持死鎖的避免,咱們在編程中必須當心地避免死鎖。
阻塞當前線程,直到指定線程執行完畢,再繼續執行
守護線程是一類特殊的線程,它和普通線程的區別在於它並非應用程序的核心部分,當一個 應用程序的全部非守護線程終止運行時,即便仍然有守護線程在運行,應用程序也將終止,反之,只要有一個非守護線程在運行,應用程序就不會終止。守護線程一 般被用於在後臺爲其它線程提供服務。調用方法 isDaemon() 來判斷一個線程是不是守護線程,也能夠調用方法 setDaemon() 將一個線程設爲守護線程。