摘要:編程
1. 經過繼承Thread類來建立並啓動多線程的方式安全
2. 經過實現Runnable接口來建立並啓動線程的方式多線程
3. 經過實現Callable接口來建立並啓動線程的方式this
4. 總結Java中建立線程的方式,比較各自優點和區別spa
Java使用Thread類表明線程,全部的線程對象都必須是Thread類或其子類的實例。每一個線程的做用是完成必定的任務,實際上就是執行一段程序流即一段順序執行的代碼。Java使用線程執行體來表明這段程序流。Java中經過繼承Thread類來建立並啓動多線程的步驟以下:線程
01. 定義Thread類的子類,並重寫該類的run()方法,該run()方法的方法體就表明了線程須要完成的任務對象
所以把run()方法稱爲線程執行體blog
02. 建立Thread子類的實例,即建立了線程對象繼承
03. 調用線程對象的start()方法來啓動該線程接口
下面程序示範了經過繼承Thread類來建立並啓動多線程:
運行部分結果:
雖然上面程序只顯式地建立並啓動了2個線程,但實際上程序有3個線程,即程序顯式建立的2個子線程和1個主線程。前面已經提到,當Java程序開始運行後,程序至少會建立一個主線程,主線程的線程執行體不是由run()方法肯定的,而是由main()方法肯定的,main()方法的方法體表明主線程的線程執行體。
該程序不管被執行多少次輸出的記錄數是必定的,一共是300條記錄。主線程會執行for循環打印100條記錄,兩個子線程分別打印100條記錄,一共300條記錄。由於i變量是MyThreadTest的實例屬性,而不是局部變量,但由於程序每次建立線程對象時都須要建立一個MyThreadTest對象,因此Thread-0和Thread-1不能共享該實例屬性,因此每一個線程都將執行100次循環。
實現Runnable接口來建立並啓動多線程的步驟以下:
01. 定義Runnable接口的實現類,並重寫該接口的run()方法,該run()方法的方法體一樣是該線程的線程執行體
02. 建立Runnable實現類的實例,並以此實例做爲Thread的target來建立Thread對象,該Thread對象纔是真正的線程對象
03. 調用線程對象的start()方法來啓動線程
須要注意的是:Runnable對象僅僅做爲Thread對象的target,Runnable實現類裏包含的run()方法僅做爲線程執行體。而實際的線程對象依然是Thread實例,只是該Thread線程負責執行其target的run()方法。
下面程序示範了經過實現Runnable接口建立線程步驟:
運行部分結果:
從該運行結果中咱們能夠看出,控制檯上輸出的內容是亂序的,並且每次結果不盡相同。這是由於:
01. 在這種方式下,程序所建立的Runnable對象只是線程的target,而多個線程能夠共享同一個target。
02. 因此多個線程能夠共享同一個線程類即線程的target類的實例屬性。
03. 往控制檯窗口print()輸出的過程並非多線程安全的,在一個線程輸出過程當中另外一個線程也能夠輸出。
爲可以保證順序輸出,咱們能夠對打印方法設置Synchronized,讓每次只能有一個進程可以訪問打印,代碼以下:
運行結果:
3.1.1 callable接口概述
也許受此啓發,從Java 5開始,Java提供了Callable接口,該接口怎麼看都像是Runnable接口的加強版,Callable接口提供了一個call()方法能夠做爲線程執行體,但call()方法比run()方法功能更強大。
01. call()方法能夠有返回值
02. call()方法能夠聲明拋出異常
所以咱們徹底能夠提供一個Callable對象做爲Thread的target,而該線程的線程執行體就是該Callable對象的call()方法。問題是:Callable接口是Java 5新增的接口,並且它不是Runnable接口的子接口,因此Callable對象不能直接做爲Thread的target。並且call()方法還有一 個返回值-----call()方法並非直接調用,它是做爲線程執行體被調用的。那麼如何獲取call()方法的返回值呢?
3.1.2 Future接口概述
Java 5提供了Future接口來表明Callable接口裏call()方法的返回值,併爲Future接口提供了一個FutureTask實現類,該實現類實現了Future接口和Runnable接口能夠做爲Thread類的target。在Future接口裏定義了以下幾個公共方法來控制它關聯的Callable任務:
1. boolcan cancel(boolean maylnterruptltRunning):試圖取消該Future裏關聯的Callable任務
2. V get():返回Callable任務裏call()方法的返回值。調用該方法將致使程序阻塞,必須等到子線程結束後纔會獲得返回值
3. V get(long timeout,TimeUnit unit):返回Callable任務裏call()方法的返回值。
該方法讓程序最多阻塞timeout和unit指定的時間,若是通過指定時間後Callable任務依然沒有返回值,
將會拋出TimeoutExccption異常
4. boolean isCancelled():若是在Callable任務正常完成前被取消,則返回true
5. boolean isDone():婦果Callable任務已完成,則返回true
3.1.3 建立並啓動有返回值的線程的步驟
01. 建立Callable接口的實現類,並實現call()方法,該cal()方法將做爲線程執行體,且該call()方法有返回值
02. 建立Callable實現類的實例,使用FutureTask類來包裝Callable對象
該FutureTask對象封裝了該Callable對象的call()方法的返回值
03. 使用FutureTask對象做爲Thread對象的target建立並啓動新線程
04. 調用FutureTask對象的get()方法來得到子線程執行結束後的返回值
下面程序示範了經過實現Callable接口建立線程步驟:
運行上面程序,將看到主線程和call()方法所表明的線程交替執行的情形,程序最後還會輸出call()方法的返回值:
上面程序中建立Callable實現類與建立Runnable實現類並無太大的差異,只是Callable的call()方法容許聲明拋出異常, 並且容許帶返回值。當主線程中當循環變量i等於20時,程序啓動以FutureTask對象爲target的線程。程序最後調用FutureTask對象 的get()方法來返回call()方法的返回值——該方法將致使主線程被阻塞,直到call()方法結束並返回爲止。
經過如下三種途徑能夠實現多線程:
01. 繼承Thread類
02. 實現Runnable接口
03. 實現Callable接口
不過實現Runnable接口與實現Callable接口的方式基本相同,只是Callable接口裏定義的方法有返回值,能夠聲明拋出異常而已。 所以能夠將實現Runnable接口和實現Callable接口歸爲一種方式。這種方式與繼承Thread方式之間的主要差異以下。
(1) 採用實現Runnable、Callable接口的方式建立多線程
線程類只是實現了Runnable接口或Callable接口,還能夠繼承其餘類。
在這種方式下,多個線程能夠共享同一個target對象,因此很是適合多個相同線程來處理同一份資源的狀況,從而能夠將CPU、代碼和數據分開,造成清晰的模型,較好地體現了面向對象的思想。
劣勢:編程稍稍複雜,若是須要訪問當前線程,則必須使用Thread.currentThread()方法。
(2) 採用繼承Thread類的方式建立多線程
劣勢:由於線程類已經繼承了Thread類,因此不能再繼承其餘父類
若是,您認爲閱讀這篇博客讓您有些收穫,不妨點擊一下右下角的【推薦】。
若是,您但願更容易地發現個人新博客,不妨點擊一下左下角的【關注我】。
若是,您對個人博客所講述的內容有興趣,請繼續關注個人後續博客,我是【Sunddenly】。
本文版權歸做者和博客園共有,歡迎轉載,但未經做者贊成必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接,不然保留追究法律責任的權利