12.2.2實現Runnable接口
一個類如果需要具備多線程的能力,也可以通過實現java.lang.Runnable接口進行實現。按照Java語言的語法,一個類可以實現任意多個接口,所以該種實現方式在實際實現時的通用性要比前面介紹的方式好一些。
使用實現Runnable接口實現多線程的示例代碼如下:
/**
* 測試類
*/
public class Test2 {
public static void main(String[] args) {
//創建對象
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
//啟動
t.start();
try{
for(int i = 0;i < 10;i++){
Thread.sleep(1000);
System.out.println("main:" + i);
}
}catch(Exception e){}
}
}
/**
* 使用實現Runnable接口的方式實現多線程
*/
public class MyRunnable implements Runnable {
public void run() {
try{
for(int i = 0;i < 10;i++){
Thread.sleep(1000);
System.out.println("run:" + i);
}
}catch(Exception e){}
}
}
該示例代碼實現的功能和前面實現的功能相同。在使用該方式實現時,使需要實現多線程的類實現Runnable,實現該接口需要覆蓋run方法,然後將需要以多線程方式執行的代碼書寫在run方法內部或在run方法內部進行調用。
在需要啟動線程的地方,首先創建MyRunnable類型的對象,然後再以該對象為基礎創建Thread類的對象,最後調用Thread對象的start方法即可啟動線程。代碼如下:
//創建對象
MyRunnable mr = new MyRunnable();
Thread t = new Thread(mr);
//啟動
t.start();
在這種實現方式中,大部分和前面介紹的方式類似,啟動的代碼稍微麻煩一些。這種方式也是實現線程的一種主要方式。
12.2.3使用Timer和TimerTask組合
最後一種實現多線程的方式,就是使用java.util包中的Timer和TimerTask類實現多線程,使用這種方式也可以比較方便的實現線程。
在這種實現方式中,Timer類實現的是類似鬧鐘的功能,也就是定時或者每隔一定時間觸發一次線程。其實,Timer類本身實現的就是一個線程,只是這個線程是用來實現調用其它線程的。而TimerTask類是一個抽象類,該類實現了Runnable接口,所以按照前面的介紹,該類具備多線程的能力。
在這種實現方式中,通過繼承TimerTask使該類獲得多線程的能力,將需要多線程執行的代碼書寫在run方法內部,然後通過Timer類啟動線程的執行。
在實際使用時,一個Timer可以啟動任意多個TimerTask實現的線程,但是多個線程之間會存在阻塞。所以如果多個線程之間如果需要完全獨立運行的話,最好還是一個Timer啟動一個TimerTask實現。
使用該種實現方式實現的多線程示例代碼如下:
import java.util.*;
/**
* 測試類
*/
public class Test3 {
public static void main(String[] args) {
//創建Timer
Timer t = new Timer();
//創建TimerTask
MyTimerTask mtt1 = new MyTimerTask("線程1:");
//啟動線程
t.schedule(mtt1, 0);
}
}
import java.util.TimerTask;
/**
* 以繼承TimerTask類的方式實現多線程
*/
public class MyTimerTask extends TimerTask {
String s;
public MyTimerTask(String s){
this.s = s;
}
public void run() {
try{
for(int i = 0;i < 10;i++){
Thread.sleep(1000);
System.out.println(s + i);
}
}catch(Exception e){}
}
}
在該示例中,MyTimerTask類實現了多線程,以多線程方式執行的代碼書寫在該類的run方法內部,該類的功能和前面的多線程的代碼實現類似。
而在該代碼中,啟動線程時需要首先創建一個Timer類的對象,以及一個MyTimerTask線程類的兌現,然後使用Timer對象的schedule方法實現,啟動線程的代碼為:
//創建Timer
Timer t = new Timer();
//創建TimerTask
MyTimerTask mtt1 = new MyTimerTask("線程1:");
//啟動線程
t.schedule(mtt1, 0);
其中schedule方法中的第一個參數mtt1代表需要啟動的線程對象,而第二個參數0則代表延遲0毫秒啟動該線程,也就是立刻啟動。
由於schedule方法比較重要,下面詳細介紹一下Timer類中的四個schedule方法:
1、public void schedule(TimerTask task,Date time)
該方法的作用是在到達time指定的時間或已經超過該時間時執行線程task.例如假設t是Timer對象,task是需要啟動的TimerTask線程對象,後續示例也采用這種約定實現,則啟動線程的示例代碼如下:
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d);
則該示例代碼的作用是在時間達到d指定的時間或超過該時間(例如2009年10月2號)時,啟動線程task.
2、public void schedule(TimerTask task, Date firstTime, long period)
該方法的作用是在時間到達firstTime開始,每隔period毫秒就啟動一次task指定的線程。示例代碼如下:
Date d = new Date(2009-1900,10-1,1,10,0,0);
t. schedule(task,d,20000);
該示例代碼的作用是當時間達到或超過d指定的時間以後,每隔20000毫秒就啟動一次線程task,這種方式會重復觸發線程。
3、public void schedule(TimerTask task,long delay)
該方法和第一個方法類似,作用是在執行schedule方法以後delay毫秒以後啟動線程task.示例代碼如下:
t. schedule(task,1000);
該示例代碼的作用是在執行該行啟動代碼1000毫秒以後啟動一次線程task.
4、public void schedule(TimerTask task,long delay,long period)
該方法和第二個方法類似,作用是在執行schedule方法以後delay毫秒以後啟動線程task,然後每隔period毫秒重復啟動線程task.
例外需要說明的是Timer類中啟動線程還包含兩個scheduleAtFixedRate方法,這兩個方法的參數和上面的第二個和第四個一致,其作用是實現重復啟動線程時的精確延時。對於schedule方法來說,如果重復的時間間隔是1000毫秒,則實際的延遲時間是1000毫秒加上系統執行時消耗的時間,例如為5毫秒,則實際每輪的時間間隔為1005毫秒。而對於scheduleAtFixedRate方法來說,如果設置的重復時間間隔為1000毫秒,系統執行時消耗的時間為5毫秒,則延遲時間就會變成995毫秒,從而保證每輪間隔為1000毫秒。
介紹完了schedule方法以後,讓我們再來看一下前面的示例代碼,如果在測試類中啟動兩個MyTimerTask線程,一種實現的代碼為:
import java.util.Timer;
/**
* 測試類
*/
public class Test4 {
public static void main(String[] args) {
//創建Timer
Timer t = new Timer();
//創建TimerTask
MyTimerTask mtt1 = new MyTimerTask("線程1:");
MyTimerTask mtt2 = new MyTimerTask("線程2:");
//啟動線程
System.out.println("開始啟動");
t.schedule(mtt1, 1000);
System.out.println("啟動線程1");
t.schedule(mtt2, 1000);
System.out.println("啟動線程2");
}
}
在該示例代碼中,使用一個Timer對象t依次啟動了兩個MyTimerTask類型的對象mtt1和mtt2.而程序的執行結果是:
開始啟動
啟動線程1
啟動線程2
線程1:0
線程1:1
線程1:2
線程1:3
線程1:4
線程1:5
線程1:6
線程1:7
線程1:8
線程1:9
線程2:0
線程2:1
線程2:2
線程2:3
線程2:4
線程2:5
線程2:6
線程2:7
線程2:8
線程2:9
從程序的執行結果可以看出,在Test4類中mtt1和mtt2都被啟動,按照前面的schedule方法介紹,這兩個線程均會在線程啟動以後1000毫秒後獲得執行。但是從實際執行效果卻可以看出這兩個線程不是同時執行的,而是依次執行,這主要是因為一個Timer啟動的多個TimerTask之間會存在影響,當上一個線程未執行完成時,會阻塞後續線程的執行,所以當線程1執行完成以後線程2才獲得了執行。
如果需要線程1和線程2獲得同時執行,則只需要分別使用兩個Timer啟動TimerTask線程即可,啟動的示例代碼如下:
import java.util.Timer;
/**
* 測試類
*/
public class Test5 {
public static void main(String[] args) {
//創建Timer
Timer t1 = new Timer();
Timer t2 = new Timer();
//創建TimerTask
MyTimerTask mtt1 = new MyTimerTask("線程1:");
MyTimerTask mtt2 = new MyTimerTask("線程2:");
//啟動線程
System.out.println("開始啟動");
t1.schedule(mtt1, 1000);
System.out.println("啟動線程1");
t2.schedule(mtt2, 1000);
System.out.println("啟動線程2");
}
}
在該示例中,分別使用兩個Timer對象t1和t2,啟動兩個TimerTask線程對象mtt1和mtt2,兩者之間不互相干擾,所以達到了同時執行的目的。
在使用上面的示例進行運行時,由於Timer自身的線程沒有結束,所以在程序輸出完成以後程序還沒有結束,需要手動結束程序的執行。例如在Eclipse中可以點擊控制台上面的紅色“Teminate”按鈕結束程序。
12.2.4 小結
關於線程的三種實現方式,就簡單的介紹這麼多。其實無論那種實現方式,都可以實現多線程,在語法允許的前提下,可以使用任何一種方式實現。比較而言,實現Runnable接口方式要通用一些。
只是從語法角度介紹線程的實現方式,還是無法體會到線程實現的奧妙,下面將通過幾個簡單的示例來體會線程功能的強大,並體會並發編程的神奇,從而能夠進入並發編程的領域發揮技術的優勢。