Java 線程對比Thread,Runnable,Callable
java 使用Thread 類代表線程,所有現場對像都必須是Thread 類或者其子類的實例。每個線程的作用是完成一定的任務,實際上就是執行一段程序流。 java 使用線程執行體來代表這段程序流。
1.繼承Thread 類創建線程
啟動多線程的步驟如下:
(1)定義Thread 類的子類,並重寫該類的run() 方法,該run() 方法的方法體就代表類線程需要完成的任務。因此把run() 方法稱為線程執行體。
(2)創建Thread 子類的實例,即創建線程對象。
(3)調用線程的star()方法來啟動該線程。
相關代碼如下:
/** * 繼承thread 的內部類,以買票例子*/ public class FirstThread extends Thread{ private int i; private int ticket = 10; @Override public void run() { for (;i<20;i++) { //當繼承thread 時,直接使用this 可以獲取當前的線程,getName() 獲取當前線程的名字// Log.d(TAG,getName()+" "+i); if(this.ticket>0){ Log.e(TAG, getName() + ", 賣票:ticket=" + ticket--); } } } } private void starTicketThread(){ Log.d(TAG,"starTicketThread, "+Thread.currentThread().getName()); FirstThread thread1 = new FirstThread(); FirstThread thread2 = new FirstThread(); FirstThread thread3 = new FirstThread(); thread1.start(); thread2.start(); thread3.start(); //開啟3個線程進行買票,每個線程都賣了10張,總共就30張票}運行結果:
可以看到3 個線程輸入的票數變量不連續,注意:ticket 是FirstThread 的實例屬性,而不是局部變量,但是因為程序每次創建線程對像都需要創建一個FirstThread 的對象,所有多個線程不共享該實例的屬性。
2.實現Runnable 接口創建線程
注意:public class Thread implements Runnable
(1)定義Runnable 接口的實現類,並重寫該接口的run()方法,該run() 方法的方法體同樣是該線程的線程執行體。
(2)創建Runnable 實例類的實例,此實例作為Thread 的target 來創建Thread 對象,該Thread 對象才是真正的對象。
相關代碼如下:
/** * 實現runnable 接口,創建線程類*/ public class SecondThread implements Runnable{ private int i; private int ticket = 100; @Override public void run() { for (;i<20;i++) { //如果線程類實現runnable 接口//獲取當前的線程,只能用Thread.currentThread() 獲取當前的線程名Log.d(TAG,Thread.currentThread().getName()+" "+i); if(this.ticket>0){ Log.e(TAG, Thread.currentThread().getName() + ", 賣票:ticket=" + ticket--); } } } } private void starTicketThread2(){ Log.d(TAG,"starTicketThread2, "+Thread.currentThread().getName()); SecondThread secondThread = new SecondThread(); //通過new Thread(target,name)創建新的線程new Thread(secondThread,"買票人1").start(); new Thread(secondThread,"買票人2").start(); new Thread(secondThread,"買票人3").start(); //雖然是開啟了3個線程,但是一共只買了100張票}運行結果:
可以看到3 個線程輸入的票數變量是連續的,採用Runnable 接口的方式創建多個線程可以共享線程類的實例的屬性。這是因為在這種方式下,程序所創建的Runnable 對像只是線程的target ,而多個線程可以共享同一個target,所以多個線程可以共享同一個線程類(實際上應該是該線程的target 類)的實例屬性。
3.使用Callable 和Future 創建線程
從java 5 開始,Java 提供了Callable 接口,該接口是runnable 的增強版,Callable 提供類一個call() 方法可以作為線程執行體,但是call() 方法的功能更強大。
(1) call() 方法可以有返回值(2) call() 方法可以聲明拋出異常
因此我們完全可以提供一個callable 對像作為Thread的target ,而該線程的執行體就是該callable 對象的call() 方法。同時java 5 提供了Future 接口來代表Callable 接口裡call() 方法的返回值,並且提供了一個futureTask 的實現類,該實現類實現類future 接口,並實現了runnable 接口―可以作為Thread 類的target.
啟動步驟如下:
(1)創建callable接口的實現類,並實現call() 方法,該call() 方法將作為線程的執行體,且該call() 方法是有返回值的。
(2)創建callable實現類的實例,使用FutureTask 類來包裝Callable對象,該FutureTask 對象封裝call() 方法的返回值。
(3)使用FutureTask 對像作為Thread對象的target創建並啟動新線程。
(4)調用FutureTask對象的get()方法來獲取子線程執行結束後的返回值。
相關代碼如下:
/** * 使用callable 來實現線程類*/ public class ThirdThread implements Callable<Integer>{ private int ticket = 20; @Override public Integer call(){ for ( int i = 0;i<10;i++) { //獲取當前的線程,只能用Thread.currentThread() 獲取當前的線程名// Log.d(TAG,Thread.currentThread().getName()+" "+i); if(this.ticket>0){ Log.e(TAG, Thread.currentThread().getName() + ", 賣票:ticket=" + ticket--); } } return ticket; } } private void starCallableThread(){ ThirdThread thirdThread = new ThirdThread(); FutureTask<Integer> task = new FutureTask<Integer>(thirdThread); new Thread(task,"有返回值的線程").start(); try { Integer integer = task.get(); Log.d(TAG,"starCallableThread, 子線程的返回值="+integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } }運行結果:
注意:Callable的call() 方法允許聲明拋出異常,並且允許帶有返回值。
程序最後調用FutureTask 對象的get()方法來返回Call()方法的返回值,導致主線程被阻塞,直到call()方法結束並返回為止。
4.三種方式的對比
採用繼承Thread 類的方式創建多線程
劣勢: 已經繼承Thread類不能再繼承其他父類。
優勢: 編寫簡單
採用繼承Runnable,Callable 接口的方式創建多線程
劣勢: 編程稍微有點複雜,如果需要訪問當前線程必須使用Thread.currentThread()
優勢:
(1)還可以繼承其他類(2)多個線程可以共享一個target 對象,所以非常適合多個相同的線程來處理同一份資源的情況,從而將cpu,代碼和數據分開,形成清晰的模型,較好的體現類面向對象的思想。
感謝閱讀,希望能幫助到大家,謝謝大家對本站的支持!