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