Java線程復習筆記
先說說線程和進程,現代操作系統幾乎無一例外地采用進程的概念,進程之間基本上可以認為是相互獨立的,共享的資源非常少。線程可以認為是輕量級的進程,充分地利用線程可以使得同一個進程中執行多種任務。Java是第一個在語言層面就支持線程操作的主流編程語言。和進程類似,線程也是各自獨立的,有自己的棧,自己的局部變量,自己的程序執行并行路徑,但線程的獨立性又沒有進程那么強,它們共享內存,文件資源,以及其他進程層面的狀態等。同一個進程內的多個線程共享同樣的內存空間,這也就意味著這些線程可以訪問同樣的變量和對象,從同一個堆上分配對象。顯然,好處是多線程之間可以有效共享很多資源,壞處是要確保不同線程之間不會產生沖突。
每個Java程序都至少有一個線程——main線程。當Java程序開始運行時,JVM就會創建一個main線程,然后在這個main線程里面調用程序的main()方法。JVM同時也會創建一些我們看不到的線程,比如用來做垃圾收集和對象終結的(garbage collection and object finalization,JVM最重要的兩種資源回收),或者JVM層面的其他整理工作。
為什么要使用線程?
1、可以使UI(用戶界面)更有效(利用多線程技術,可以把時間較長的UI工作交給專門的線程,這樣UI的主線程就不會被長期占用,界面就會流暢而不停滯)
2、有效利用多進程系統(單線程+多進程,太浪費系統資源了)
3、簡化建模
4、執行異步處理或者后臺處理(不同的線程做不同的工作)
線程的生命周期:
通常有兩種方法創建一個線程,1、implement Runnable接口,2、繼承Thread類
創建完成后,這個線程就進入了New State,直到它的start()方法被調用,它就進入了Runnable狀態。
一個線程從Running State進入Terminated / Dead State標志著線程的終結,正常情況下有這么幾種可能性:
1、線程的run()執行結束
2、線程拋出沒有捕捉到的異常或者錯誤
當一個Java程序所有的非守護進程(Daemon Thread,即守護進程,負責一些包括資源回收在內的任務,我們無法結束這些進程)結束時,程序宣告執行結束。
Java Thread的重要方法必須熟悉。
join():目標線程結束之前調用線程將會被Block,例如在main線程中創建了一個thread1線程,調用 thread1.join(),這就意味著thread1將優先執行,在thread1結束后main thread才會繼續。一個join()方法的使用案例:將一個任務(比如從1萬個元素的數組中選出最大值)分拆成10個小任務(每個小任務負責1000 個)分配給10個線程,調用它們的start(),然后分別調用join(),以確保10個任務都完成(分別選出了各自負責的1000個元素中的最大值)后,主任務再進行下去(從10個結果中挑出最大值)。
sleep():使當前線程進入Waiting State,直到指定的時間到了,或者被其他線程打斷,從而回到Runnable State。
wait():使調用線程進入Waiting State,直到被打斷,或者時間到,或者被其他線程使用notify(),notifyAll()叫醒。
notify():這個方法被一個對象調用時,如果有多個線程在等待這個對象,這些處于Waiting State的線程中的一個會被叫醒。
notifyAll():這個方法被一個對象調用時,如果有多個線程在等待這個對象,這些處于Waiting State的線程都會被叫醒。
多線程共享資源是討論最多的話題,也是最容易出問題的地方之一,Java定義了兩個關鍵字,synchronized和volatile,用來幫助共享的變量在多線程情況下能夠正常工作。
synchronized一方面確保同一時間內只有一個線程能夠執行一段受保護的代碼,并且這個線程對數據(變量)進行的改動對于其他線程是可見的。這里包含兩層意思:前者依靠lock(鎖)來實現,當一個線程處理一段受保護代碼時,該線程就擁有lock,只有它釋放了這個lock,其他線程才有可能獲得并訪問這段代碼;后者由JVM機制實現,對于受synchronized保護的變量,需要讀取時(包括獲取lock)會首先廢棄緩存(invalidate cache),進而直接讀取main memory上的變量,完成改動時(包括釋放lock)會flush緩存,強行把所有改動更新到main memory。
為了提高performance,處理器都是會利用緩存來保存一些變量儲存在內存中的地址,這樣就存在一種可能性,在一個多進程架構中,一個內存地址在一個進程的緩存中被修改了,其他進程并不會自動獲得更新,于是不同進程上的2個線程就會看到同一個內存變量的兩個不同值(因為兩個緩存中的保存的內存地址不同,一個被修改過)。Volatile關鍵字可以有效地控制原始類型變量(primitive variable,比如integer,boolean)的單一實例:當一個變量被定義為volatile的時候,無論讀寫,都會繞過緩存而直接對 main memory進行操作。
關于Java的鎖(Locking)有一個問題需要注意:一段被lock保護的代碼并不意味著就一定不能被多線程同時訪問,而只意味著不能被等待同一個lock的多線程同時訪問。
對于絕大多數的synchronized方法,它的lock就是調用方法的實例對象;對于static synchronized方法,它的lock是定義方法的類(因為static方法是每個類只有一份copy,而不是每個實例都有一份copy)。因此,即使一個方法被synchronized保護了,多線程仍然可以同時調用這個方法,只要它們是調用不同實例上的這個方法。
synchronized代碼塊稍微復雜一些,一方面它也需要和synchronized方法一樣定義lock的類型,另一方面必須考慮如果最小化被保護的代碼塊,即能不放到synchronized里面就不放進去,比如局部變量的訪問通通不需要保護,因為局部變量本身就只存在于單線程上。
下面兩種加鎖的方法是等效的,都是以Point類的實例為lock(即多線程可以同時訪問不同Point實例的synchronized setXY()方法):
public class Point { public synchronized void setXY(int x, int y) { this.x = x; this.y = y; } }
public class Point { public void setXY(int x, int y) { synchronized (this) { this.x = x; this.y = y; } } }死鎖(deadlock)是多線程編程中最怕遇到的情況,簡單來說就是線程1擁有對象A的lock,等待獲取對象B的lock,線程2擁有對象B的 lock,等待獲取對象A的lock,這樣就沒完沒了了。怎么防止deadlock是一個大話題,可以寫一本書,簡單來說的話就是當線程需要獲取多個 lock的時候(比如線程1和2都要獲取對象A和B的lock),永遠按照一定的次序來。比如如果線程1和2都是先獲取對象A的lock,再獲取對象B,那就不會出現上面的deadlock了,因為如果1獲得了A lock,2就得等,而不是去獲得B lock。
轉自:http://blog.csdn.net/qinjienj/article/details/7578582