建立線程有兩種方式:第一種是繼承Thread類,第二種是實作Runnable接口。下面詳細介紹兩種線程建立的方式,這個内容為必須掌握的。到最後會對比兩種方式建立線程的優勢和劣勢。
1繼承Thread類建立線程:
1,建立一個類,繼承Thread類并覆寫Thread類的run方法。為什麼要覆寫run方法?該run方法的方法體就是代表了線程要完成的任務,是以,run方法也稱為線程執行體。
2,建立Thread子類的執行個體,即建立了線程對象。
3,用線程對象調用start方法啟動線程。
下面來看看代碼是如何實作的:
public class ThreadCreat
{
public static void main(String args[]){
MyThread mt = new MyThread(); //建立Thread子類執行個體對象
mt.start(); //通過子類對象的start方法開啟線程
for(int x=0;x<10;x++)
System.out.println(Thread.currentThread().getName()+"-->"+x);
}
}
class MyThread extends Thread //建立一個類繼承Thread
{
public void run(){ //覆寫run方法,方體即線程體
for(int i=0;i<10;i++)
System.out.println(this.getName()+"--->"+i);
}
}
---------- 運作java程式 ----------
main-->0
main-->1
Thread-0--->0
Thread-0--->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
Thread-0--->2
Thread-0--->3
Thread-0--->4
Thread-0--->5
Thread-0--->6
Thread-0--->7
Thread-0--->8
Thread-0--->9
輸出完成 (耗時 0 秒) - 正常終止
在列印時,我使用了Thread類中靜态方法currentThread ,調用該方法可以傳回目前正在執行的線程對象(Thread)。從輸出可以看到,在本程式中,開啟了兩條線程,一條是主線程:
main,另外一條是Thread-0線程。這兩條線程是随機交替進行的。對于單核cpu在某一個時間點上隻能執行一個命名,由于cpu切換的非常快,人是無法察覺它的切換,給人的感覺實在同時執行多個任務。知道了這一點,有利于我們了解多線程。
下面再來看看為何會出現上面的輸出結果:
2線程建立的第二種方式:實作Runnable接口
1,建立一個類實作Runnable接口,并覆寫run方法,run方法體就是線程體。
2,建立一個Thread本類對象,在建立該對象時,使用的是其Thread(Runnable target)構造方法,将Runnable 子類對象作為參數傳遞給該構造方法。
3,調用Thread的start方法,啟動線程。
下面看看代碼是如何實作的:
public class ThreadCreat2
{
public static void main(String args[]){
new Thread(new MyThread2()).start();//将實作了Runnable接口的類的執行個體對象作為 //參數傳遞給Thread類的執行個體對象
for(int i=0;i<10;i++)
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
class MyThread2 implements Runnable //實作Runnable接口
{
public void run(){
for(int i=0;i<10;i++)
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
---------- 運作java程式 ----------
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
Thread-0-->0
Thread-0-->1
main-->7
main-->8
main-->9
Thread-0-->2
Thread-0-->3
Thread-0-->4
Thread-0-->5
Thread-0-->6
Thread-0-->7
Thread-0-->8
Thread-0-->9
輸出完成 (耗時 0 秒) - 正常終止
從運作結果也可以看到,主線程(main)和Thread-0線程時随機交替執行的。
看完兩種建立線程的方式後,我們來對比下兩種線程的優勢和劣勢:
采用繼承Thread類方式完成多線程建立:
劣勢是:因為線程類已經繼承了Thread類,是以不能再繼承其他父類。
優勢是:編寫簡單,如果需要通路線程,無需使用Thread.currentThread()方法,直接
使用this即可獲得目前線程。
采用實作Runnable接口方式的多線程:
優勢是:線程類隻是實作了Runnable接口,還可以繼承其他類。在這種方式下,可以多個線程共享同一個target對象,是以非常适合多個相同線程來處理同一份資源的情況,較好的展現了面向對象的思想。
劣勢是:程式設計稍稍複雜,如果需要通路目前線程,必須使用Thread.currentThread()方法。
推薦使用實作Runnable接口的方式建立線程對象。
3,售票程式
現在用多線程來模拟一個實際情況:
現在有四個售票廳,同時在賣100張票,這100張票是他們共有的,售完即停。
我們先來看下代碼是如何實作的:
public class SoldTickets
{
public static void main(String args[]){
Tickets t1 = new Tickets();
Tickets t2 = new Tickets();
Tickets t3 = new Tickets();
Tickets t4 = new Tickets();//建立四個線程對象
t1.start(); //開啟四個賣票線程
t2.start();
t3.start();
t4.start();
}
}
class Tickets extends Thread
{
public static int ticket = 100; //這裡加靜态時為了保證賣的票是100張
static Object obj = new Object(); //要保證鎖的唯一性,就需要用static關鍵字修飾
public void run(){
while(true){
synchronized(obj){ //線程使用的是同一個鎖
if(ticket>0){
System.out.println(this.getName()+"-->"+ticket);
ticket--;
}
}
}
}
}
上面是通過繼承Thread類來完成賣票程式的。重點是要保證多個線程處理的是同一份資料,其二,關鍵代碼要用同步代碼塊。在以上代碼中是如何實作的了?
我們來看高亮的注釋:
由于是繼承Thread類,要開啟多條線程,就會建立多個對象,為了保證賣的票是唯一的
public static int ticket = 100; 不管我們建立多少對象,用static關鍵字修飾的成員變量,在記憶體中隻有一份。至于另一個問題,同步代碼塊和鎖的問題會留在下一節會詳細講解,并給出為何要同步的原因。
再來看看實作Runnable接口的類是如何完成賣票程式的:
public class SoldTickets2
{
public static void main(String args[]){
TicketsDemo td = new TicketsDemo();
Thread t1 = new Thread(td);
Thread t2 = new Thread(td);
Thread t3 = new Thread(td);
Thread t4 = new Thread(td);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class TicketsDemo implements Runnable
{
public int tickets = 100; //注意這裡并沒有加static
Object obj = new Object(); //這裡也沒有加static
public void run(){
while(true){
synchronized(obj){ //加鎖
if(tickets>0){
System.out.println(Thread.currentThread().getName()+"-->"+tickets);
tickets--;
}
}
}
}
}
我們來看看,這段代碼是如何保證賣的是共同的100張票。
由于我們是采用的是實作Runnable接口方式來建立線程的。我們隻需要建立一個TicketsDemo類執行個體對象;在建立線程對象時通過建立Thread本類對象來完成的,同時将TicketsDemo作為參數傳遞給Thread的有參構造器,完成線程的建立。這樣一來,我們可以用同一個TicketsDemo對象來建立多個線程對象。這就保證了票的唯一性,鎖的唯一性也保證了。