程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 什麼是線程,如何創建線程

什麼是線程,如何創建線程

編輯:關於JAVA

如果你學習過操作系統,那麼一定對進程的概念非常熟悉,其實,幾乎每種操作系統都支持進程——進程就是在某種程度上相互隔離的、獨立運行的程序。進程的引入大大提高了任務並發執行的效率,但是,進程也因為耗費資源太大等缺陷限制了它在並行處理方面的發展。不過線程的引入改變了這一狀況,線程也稱做輕量級進程。就像進程一樣,線程在程序中是獨立的、並發的執行路徑,每個線程有它自己的堆棧、自己的程序計數器和自己的局部變量。但是,與獨立的進程相比,進程中的線程之間的獨立程度要小。它們共享內存、文件句柄和其他每個進程應有的狀態。

線程的出現也並不是為了取代進程,而是對進程的功能作了擴展。進程可以支持多個線程,它們看似同時執行,但相互之間並不同步。一個進程中的多個線程共享相同的內存地址空間,這就意味著它們可以訪問相同的變量和對象,而且它們從同一堆中分配對象。盡管這讓線程之間共享信息變得更容易,但你必須小心,確保它們不會妨礙同一進程裡的其他線程。

目前,大多數的操作系統都支持線程,包括 Linux、Solaris 和 Windows NT/2003,都可以利用多個處理器調度線程在任何可用的處理器上執行。如果某個程序有多個活動線程,那麼還可以同時調度多個線程。在精心設計的程序中,使用多個線程可以提高程序吞吐量和性能。在某些情況下,使用線程還可以使程序編寫和維護起來更簡單。雖然線程可以大大簡化許多類型的應用程序,但是過度使用線程可能會危及程序的性能及其可維護性。不要忘記,線程同樣也在消耗資源。因此,在不降低性能的情況下,創建一定數量的線程才是真正有用的。

Java 成為第一個在語言本身中顯式地包含線程的主流編程語言,它使針對線程的操作不再那麼神秘,因為它已經不再把線程看做是底層操作系統的工具。不過,雖然Java 提供的線程工具和 API 看似簡單,但是編寫有效使用線程的復雜程序並不十分容易。因為有多個線程共存在相同的內存空間中並共享相同的變量,所以必須小心使用,從而確保線程不會互相干擾。本章就將介紹關於線程的各種用法。

4.1 什麼是線程,如何創建線程

問題

在網絡中,數據傳輸的速率是遠遠低於計算機的處理能力的,就本地文件的讀寫而言,其讀寫速度也遠低於CPU的處理能力。在傳統的單任務環境中,程序必須等待上一個任務完成以後才能執行下一個任務。例如當前某個程序運行過程中需要等待用戶鍵盤輸入的數據,由於鍵盤輸入的速度相對於CPU的執行速度而言要慢得多,這時候CPU就會空閒下來,直到收到鍵盤輸入的數據,程序繼續執行。

解決這種問題的辦法就是引入多線程技術,Java提供了這樣的技術,利用多線程技術編寫的程序,可以使計算機同時並行運行多個相對獨立的任務。例如,可以創建一個線程來負責數據的輸入和輸出,而創建另一個線程在後台進行其他的數據處理,如果輸入輸出線程在接收數據時阻塞,而處理數據的線程仍然可以運行。這樣,多線程程序設計就大大提高了程序執行效率和處理能力。那麼什麼是線程,如何創建線程呢?

解決思路

在掌握如何創建線程之前,先要了解一下什麼是進程。進程(process)本質上是一個執行的程序。操作系統引入進程以後就允許計算機可以同時運行兩個或兩個以上的程序,這就是多任務的處理模式。每一個進程都有自己獨立的一塊內存空間、一組系統資源。在進程概念中,每一個進程的內部數據和狀態都是完全獨立的。例如,基於進程的多任務處理功能不僅可以使我們在操作系統中使用記事本編輯文檔,而且還可以同時聽歌和看電影。

線程與進程相似,是一段完成某個特定功能的代碼,是程序中單個順序的流控制,但與進程不同的是,同類的多個線程是共享同一塊內存空間和一組系統資源的,而線程本身的數據通常只有微處理器的寄存器數據,以及一個供程序執行時使用的堆棧。所以系統在產生一個線程,或者在各個線程之間切換時,負擔要比進程小得多,正因如此,線程也被稱為輕型進程(light-weight process)。一個進程中可以包含多個線程。

多線程則指的是在單個程序中可以同時運行多個不同的線程,執行不同的任務。多線程意味著一個程序的多行語句可以看上去幾乎在同一時間內同時運行。

同時運行的含義是指操作系統中管理的時間片會平均地分給每個線程,從而保證所有的線程都能夠在極短的時間內得到處理。每一時間片內只能執行一個線程,但由於時間片是一個很小的時間單元,每一個線程又是很小的代碼段,因此,操作系統能夠在很短的時間內進行線程的切換,所以看起來就好像是多個任務可以同時執行。

Java提供了線程類Thread來創建多線程的程序。其實,創建線程與創建普通的類的對象的操作是一樣的,而線程就是Thread類或其子類的實例對象。每個Thread對象描述了一個單獨的線程。要產生一個線程,有兩種方法:

◆需要從Java.lang.Thread類派生一個新的線程類,重載它的run()方法;

◆實現Runnalbe接口,重載Runnalbe接口中的run()方法。

具體步驟

1、擴展Thread類來創建線程

首先,需要通過創建一個新類來擴展Thread類,這個新類就成為Thread類的子類。接著在該子類中重寫Thread類的run()方法,此時方法體內的程序就是將來要在新建線程中執行的代碼。

示例如下所示:

class SubThread extends Thread

{



public void run()

{

// 新建線程所要完成的工作

}



}

接著要創建該子類的對象,此時一個新的線程就被創建了,創建新線程時會用到Thread 類定義的如下兩個構造函數:

◆public Thread()

◆public Thread(String name)

其中,第一個構造函數是Thread類默認的構造函數,不指定參數;第二個構造函數可以為新建的線程指定一個名稱,該名稱就是字符串參數name的值。

建立了新的線程對象以後,它並不運行,而是直到調用了該對象的start()方法,該方法在Thread 類中定義,在Thread 類的子類中被覆蓋。它的作用是啟動一個新的線程,並在該線程上運行子類對象中的run()方法。

start()方法聲明格式為:

public void start()

示例如下所示:

SubThread s = new SubThread();   // 創建Thread子類的對象

s.start(); // 啟動一個新的線程

當一個類繼承Thread類時,它必須重寫run()方法,這個方法是新線程的入口。如     果Thread類的這個子類沒有覆蓋run()方法,那麼程序會調用Thread類的run()方法,只    不過該run()方法什麼也不做,此時新線程一創建就結束了。這種線程對程序來說是沒有  任何意義的,所以在這裡提醒讀者在創建線程的時候一定不要忘了覆蓋Thread類的run()方法。

下面舉一個完整的例子來演示通過擴展Thread類創建線程的過程。程序代碼如下所示:

// 例4.1.1  ThreadDemo.Java

class NewThread extends Thread  // 通過繼承Thread類來創建一個新的線程

{

NewThread()// 構造方法

{    

super("Thread Demo");    // 定義線程的名字

System.out.println("New thread: " + getName());

}

public void run()  // 覆寫run()方法,這是線程的入口

{  

while( true )

{

System.out.println(Thread.currentThread().getName()+

"  is running");

}

}

}

class ThreadDemo

{

public static void main(String args[])

{

NewThread thd = new NewThread(); // 創建一個新線程

thd.start();          //啟動線程,調用NewThread類對象的run()方法

}

}

可以看到,新的線程是由實例化NewThread類的對象創建的,該NewThread類可以通過繼承Java.lang.Thread類來得到。其中,在NewThread類中,構造函數裡調用了super()方法。該方法將調用父類Thread下列形式的構造函數:

public Thread(String threadName)

這裡,threadName指定線程名稱(當然也可以不指定線程的名稱,而由系統自動為新建線程提供名稱)。在NewThread類中通過覆寫run()方法來規定線程所要實現的內容。此外,需要注意的是,啟動新線程執行時必須調用start()方法。程序結果如圖4.1.1所示:

圖4.1.1 創建一個新的線程並啟動它

在上面的代碼中,使用了Thread類的currentThread()靜態方法來獲得當前程序執行時所對應的那個線程對象,又通過線程對象的getName()方法,得到了當前線程的名字。這些方法都可以在JDK幫助文檔中查到。因此,善於利用JDK幫助文檔來獲取有關類的更多信息,可以方便程序的編寫。

在上面代碼的run()方法中,由於循環條件始終為true,因此,屏幕上會不斷地輸出Thread Demo is running,新建的線程永遠不會結束,這當然不是所希望的結果。這裡所希望的是可以合理的設置循環條件來有效地控制線程的終止。所以,在run()方法中使用到循環控制的時候一定要小心使用,否則局面難以控制。

其實,針對前面的程序做一些改動。可以讓這個程序實現一個非常有用的功能。

// 例4.1.2  ThreadDemo2.Java

class NewThread extends Thread // 通過繼承Thread類來創建一個新的線程



NewThread(String name)   // 構造方法

{

super(name);

}

public void run()  // 重寫run()方法,這是線程的入口

{

for( int i=10 ; i>0 ;i--)  // 循環執行10次

{

try

{

System.out.println("left time: "+ i);

Thread.sleep(1000);   //當前線程睡眠1000毫秒

}catch(InterruptedException e){     //處理異常

System.out.println(e.getMessage());

}

}

System.out.println("game is over,bye!");

}

}

class ThreadDemo2

{

public static void main(String args[])

{

NewThread thd = new NewThread("Thread Demo"); // 創建一個新的線程

thd.start();          //啟動線程,調用NewThread類對象的run()方法

}

}

程序輸出結果如圖4.1.2 所示:

圖4.1.2  線程應用

 

通過這個程序看到了什麼?在run()方法體中,實現了一個倒計時的功能。線程通過循環控制,每隔一秒輸出一次剩余的時間,循環結束時輸出"game is over,bye!",線程也隨之結束。可見這個程序中新建的線程不是死循環,而是通過一些條件來對線程的起始進行了控制,從而實現了倒計時的功能。

在這個程序中,還可以看到,在給線程起名字的時候可以通過創建線程的時候來實現。因為查閱JDK的幫助文檔,會發現Thread類除了默認的構造函數之外,還有很多帶參數的構造函數,只不過在這裡是用到了public Thread(String name)這個構造方法。

並不是在創建線程對象的時候給線程起個名字就可以了,還應該在線程類的子類中定義相應的構造函數才行,這個構造函數的形式如下:

SubThread(String name)   // 線程子類的構造方法

{

super(name);

}

如果不這樣做,程序編譯會提示錯誤,讀者可以想想為什麼。也可以將上面程序中NewThread類的構造方法注釋掉,編譯一下程序,看到錯誤提示後,再去思考這個問題。

在這個程序中還用到了try…catch語句,它用來捕獲程序中可能發生的異常,而產生異常的原因是程序中使用了Thread.sleep()這樣的方法。通過查閱JDK的幫助文檔,可以看到線程類的sleep()方法的完整格式如下:

public static void sleep(long millis) throws InterruptedException

看到這個throws關鍵字,想必讀者就應知道為什麼使用try…catch語句了。由於這個方法可能會拋出一個中斷異常,因此,有必要在程序調用這個方法時對可能發生的異常進行處理。此外,這個方法是靜態的,所以可以通過類名直接調用。

2、實現Runnable接口來創建線程

除了擴展Thread類可以創建線程之外,還可以通過定義一個實現了Runnable接口的類來創建線程。為了將來程序執行時可以進入線程,在這個類中必須實現Runnable接口中唯一提供的run()方法。

示例如下所示:

class OneThread implements Runnable

{



public void run()

{

// 新建線程所要完成的工作

}

}

當定義好一個實現了Runnable接口的類以後,還不能直接去創建線程對象,要想真正去創建一個線程,還必須在類的內部實例化一個Thread類的對象。此時,會用到Thread 類定義的如下兩個構造函數:

public Thread(Runnable target)

public Thread(Runnable target,String name)

在這兩個構造函數中,參數target定義了一個實現了Runnable接口的類的對象引用。新建的線程將來就是要執行這個對象中的run()方法。而新建線程的名字可以通過第二個構造方法中的參數name來指定。

示例如下所示:

OneThread onethread = new OneThread();

Thread newthread = new Thread(onethread);

此時,新線程對象才被創建,如果想要執行該線程的run()方法,則仍然需要通過調用start()方法來實現。例如:

newthread.start();

要想創建新的線程對象,這兩條語句缺一不可。此後程序會在堆內存中實實在在地創建一個OneThread類的實例對象,該對象中包含了一個線程對象newthread。newthread對象會通過調用start()方法來執行它自己的run()方法。隨著run()方法的結束,線程對象newthread的生命也將結束,但是onethread對象還會存在於堆內存當中。如果希望在實際編程當中一旦線程結束,即釋放與線程有關的所有資源,可以使用創建匿名對象的方法來創建這個線程,格式如下所示:

new Thread(new OneThread).start();

這樣一來,該線程一旦運行結束,所有與該線程有關的資源都將成為垃圾,這樣就可以在特定的時間內被Java的垃圾回收機制予以回收,釋放所占用內存,提高程序的效率。

下面這個程序是通過實現Runnable接口來創建的線程,可以將它和前面的例4.1.2的程序進行比較。

// 例4.1.3  ThreadDemo3.Java

class NewThread implements Runnable // 實現了Runnable接口



public void run() // 覆寫Runnable 接口中唯一的run()方法,這是線程的入口

{

for( int i=10 ; i>0 ;i--)

{

try

{

System.out.println("left time: "+ i);

Thread.sleep(1000);   //當前線程睡眠1000毫秒

}catch(InterruptedException e){     //處理異常

System.out.println(e.getMessage());

}

}

System.out.println("game is over,bye!");

}

}

class ThreadDemo3

{

public static void main(String args[])

{

NewThread newthread = new NewThread();

Thread thd = new Thread(newthread, "Thread Demo")

thd.start();          //啟動線程,調用run()方法

}

}

編譯並運行這個程序,可以看到程序執行的結果和例4.1.2的程序輸出的結果是完全一樣的,因此,讀者可以在創建線程的時候選擇任意一種方式來實現。

專家說明

為了使程序達到優異的性能,可以利用創建線程來完成那些任務。因為一個線程就是一個獨立的執行通道,在沒有特殊的要求之下,多個線程之間彼此獨立運行,互不干擾。而且帶線程的程序通常比沒有帶線程的程序運行得要快,因此線程常用在網絡和圖形用戶界面等程序設計當中,這一優勢在多處理器的計算機上更加明顯。本節中不僅要理解什麼是線程,而且還應掌握兩種創建線程的方法,為以後在程序中使用多線程技術打下堅實的基礎。

專家指點

學習了本節以後,有很多讀者都會問到這樣的問題:為什麼Java要提供兩種方法來創建線程呢?它們都有哪些區別?相比而言,哪一種方法更好呢?

在Java中,類僅支持單繼承,也就是說,當定義一個新的類的時候,它只能擴展一個外部類.這樣,如果創建自定義線程類的時候是通過擴展Thread類的方法來實現的,那麼這個自定義類就不能再去擴展其他的類,也就無法實現更加復雜的功能。因此,如果自定義類必須擴展其他的類,那麼就可以使用實現Runnable接口的方法來定義該類為線程類,這樣就可以避免Java單繼承所帶來的局限性。

還有一點最重要的就是使用實現Runnable接口的方式創建的線程可以處理同一資源,從而實現資源的共享,這一點會在第4.2節中介紹。

相關問題

如果程序當中沒有顯式地創建線程,那麼是不是程序中就沒有線程存在呢?其實,當Java程序啟動時,有一個線程立刻就會運行,該線程是自動創建的,它就是程序的主線程(main thread),它在程序開始時就執行了。

主線程的產生可以完成兩方面的任務:

◆它是產生其他子線程的線程

◆通常它必須最後完成執行,這樣它可以執行各種關閉操作。

下面舉一個能夠顯示主線程運行狀態的例子,程序代碼如下。

// 例4.1.4  ThreadDemo4.Java

class NewThread extends Thread

{

NewThread(String name)

{

super(name);

}

public void run()   

{

for (int count = 10,row = 0; row < 10; row++)  //輸出10行*

{  

for (int i = 0; i < count; i++)

System.out.print('*');

System.out.println();

}

System.out.println(currentThread().getName()+"  is over");

}

}

class ThreadDemo4

{

public static void main(String argv[])

{

// 下面兩行代碼用來新建一個線程

NewThread thd = new NewThread("new Thread"); 

thd.start();       

// 下面的代碼用來控制主線程

for(int i=0;i<10;i++)

{

System.out.println(Thread.currentThread().getName()+"  is

running");

}

System.out.println(Thread.currentThread().getName()+" is over");

}

}

盡管主線程在程序啟動時自動創建,但它可以被一個Thread對象控制。要想這樣做,必須在主類中調用Thread類的方法currentThread()來獲得主線程的一個引用。

再來研究一下這個Thread類的currentThread()方法到底都做了什麼,通過查閱JDK幫助文檔,發現該方法是Thread類的公有的靜態成員。它的語法形式如下:

static Thread currentThread( )

該方法如果在主類中被使用,那麼它將返回主線程的引用,如果在一個Thread類的派生子類中被使用,則它將返回由該派生子類所產生的新線程的引用。因此,一旦獲得主線程的引用,就可以像控制其他線程那樣控制主線程了。

編譯程序並運行,得到這樣的結果:

main is running

main is running

main is running

main is running

**********

**********

**********

main is running

main is running

main is running

main is running

main is running

main is running

main is over

**********

**********

**********

**********

**********

**********

**********

new Thread is over

從結果來看,並不是先輸出10行“**********”符號,然後再輸出10行"main is running",而是交錯輸出。這就是因為采用了線程的方式,由於操作系統在調度線程的時候並不是按順序進行的,而是將時間片輪流分給每一個線程,具體每一次分給哪個線程也是不確定的,但可以保證在很短的時間內,每一個線程都有被執行的機會,因此程序顯示出了上面的結果。當然,如果再次運行程序,還會得到不同的輸出結果。當循環的次數越多,輸出的結果越能說明這個問題。

隨機性地分配時間片給一個線程使用並不是我們所期望的結果,引入線程是為了能夠充分利用系統資源,但如果程序中存在很多線程,那麼程序的輸出結果會因為這種隨機性而變得面目全非。為了更好地控制多個線程之間能夠合理地使用時間片,可以通過一些方法來合理地調度線程,從而既能夠正確地輸出期望的結果,又充分利用系統資源。想了解有關內容,讀者可以參考第4.2節。

前面提到程序的生命周期時是這樣介紹的:程序從main()方法這個入口進入,在main()方法中可以調用其他方法和成員或者其他類及類的方法和成員,main()方法執行完畢,整個程序也就結束了。但是在這個程序中可以看到這樣的情況,即new Thread這個線程結束的時間是在主線程結束之後。這就說明Java允許其他線程在主線程之後結束。也證明這樣一個事實,那就是,只有當所有線程都執行結束時,整個程序才真正結束。可見,前面的說法並無錯誤,只不過這裡引入了線程的概念。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved