| 在Java語言產生前,傳統的程式設計語言的程式同一時刻只能單任務操作,效率非常低,例如程式往往在接收資料輸入時發生阻塞,只有等到程式獲得資料後才能繼續運行。 隨著Internet的迅猛發展,這種狀況越來越不能讓人們忍受:如果網路接收資料阻塞,背景程式就處於等待狀態而不繼續任何操作,而這種阻塞是經常會碰到的,此時CPU資源被白白的閑置起來。如果在背景程式中能夠同時處理多個任務,該多好啊!應Internet技術而生的Java語言解決了這個問題,多線程程式是Java語言的一個很重要的特點。在一個Java程式中,我們可以同時並行運行多個相對獨立的線程,例如,我們如果建立一個線程來進行資料輸入輸出,而建立另一個線程在後台進行其它的資料處理,如果輸入輸出線程在接收資料時阻塞,而處理資料的線程仍然在運行。多線程程式設計大大提高了程式執行效率和處理能力。 線程的建立 我們知道Java是物件導向的程式語言,用Java進行程式設計就是設計和使用類,Java為我們提供了線程類Thread來建立線程,建立線程與建立普通的類的對象的操作是一樣的,而線程就是Thread類或其子類的執行個體對象。下面是一個建立啟動一個線程的語句: Thread thread1=new Thread(); file://聲明一個對象執行個體,即建立一個線程; Thread1.run(); file://用Thread類中的run()方法啟動線程; 從這個例子,我們可以通過Thread()構造方法建立一個線程,並啟動該線程。事實上,啟動線程,也就是啟動線程的run()方法,而Thread類中的run()方法沒有任何動作陳述式,所以這個線程沒有任何操作。要使線程實現預定功能,必須定義自己的run()方法。Java中通常有兩種方式定義run()方法: 通過定義一個Thread類的子類,在該子類中重寫run()方法。Thread子類的執行個體對象就是一個線程,顯然,該線程有我們自己設計的線程體run()方法,啟動線程就啟動了子類中重寫的run()方法。 通過Runnable介面,在該介面中定義run()方法的介面。所謂介面跟類非常類似,主要用來實現特殊功能,如複雜關係的多重繼承功能。在此,我們定義一個實現Runnable() 介面的類,在該類中定義自己的run()方法,然後以該類的執行個體對象為參數調用Thread類的構造方法來建立一個線程。 線程被實際建立後處於待命狀態,啟用(啟動)線程就是啟動線程的run()方法,這是通過調用線程的start()方法來實現的。 下面一個例子實踐了如何通過上述兩種方法建立線程並啟動它們: // 通過Thread類的子類建立的線程; class thread1 extends Thread { file://自訂線程的run()方法; public void run() { System.out.println("Thread1 is running…"); } } file://通過Runnable介面建立的另外一個線程; class thread2 implements Runnable { file://自訂線程的run()方法; public void run() { System.out.println("Thread2 is running…"); } } file://程式的主類' class Multi_Thread file://聲明主類; { plubic static void mail(String args[]) file://聲明主方法; { thread1 threadone=new thread1(); file://用Thread類的子類建立線程; Thread threadtwo=new Thread(new thread2()); file://用Runnable介面類的對象建立線程; threadone.start(); threadtwo.start(); file://strat()方法啟動線程; } } 運行該程式就可以看出,線程threadone和threadtwo交替佔用CPU,處於並行運行狀態。可以看出,啟動線程的run()方法是通過調用線程的start()方法來實現的(見上例中主類),調用start()方法啟動線程的run()方法不同於一般的調用方法,調用一般方法時,必須等到一般方法執行完畢才能夠返回start()方法,而啟動線程的run()方法後,start()告訴系統該線程準備就緒可以啟動run()方法後,就返回start()方法執行調用start()方法語句下面的語句,這時run()方法可能還在運行,這樣,線程的啟動和運行並行進行,實現了多任務操作。 線程的優先順序 對於多線程程式,每個線程的重要程度是不盡相同,如多個線程在等待獲得CPU時間時,往往我們需要優先順序高的線程優先搶佔到CPU時間得以執行;又如多個線程交替執行時,優先順序決定了層級高的線程得到CPU的次數多一些且時間多長一些;這樣,高優先順序的線程處理的任務效率就高一些。 Java中線程的優先順序從低到高以整數1~10表示,共分為10級,設定優先權是通過調用線程對象的setPriority()方法,如上例中,設定優先權的語句為: thread1 threadone=new thread1(); file://用Thread類的子類建立線程; Thread threadtwo=new Thread(new thread2()); file://用Runnable介面類的對象建立線程; threadone.setPriority(6); file://設定threadone的優先順序6; threadtwo.setPriority(3); file://設定threadtwo的優先順序3; threadone.start(); threadtwo.start(); file://strat()方法啟動線程; 這樣,線程threadone將會優先於線程threadtwo執行,並將佔有更多的CPU時間。該例中,優先順序設定放線上程啟動前,也可以在啟動後進行設定,以滿足不同的優先順序需求。 線程的(同步)控制 一個Java程式的多線程之間可以共用資料。當線程以非同步方式訪問共用資料時,有時候是不安全的或者不和邏輯的。比如,同一時刻一個線程在讀取資料,另外一個線程在處理資料,當處理資料的線程沒有等到讀取資料的線程讀取完畢就去處理資料,必然得到錯誤的處理結果。這和我們前面提到的讀取資料和處理資料並行多任務並不矛盾,這兒指的是處理資料的線程不能處理當前還沒有讀取結束的資料,但是可以處理其它的資料。 如果我們採用多線程同步控制機制,等到第一個線程讀取完資料,第二個線程才能處理該資料,就會避免錯誤。可見,線程同步是多線程編程的一個相當重要的技術。 在講線程的同步控制前我們需要交代如下概念: 1 用Java關鍵字synchonized同步對共用資料操作的方法 在一個對象中,用synchonized聲明的方法為同步方法。Java中有一個同步模型-監視器,負責管理線程對對象中的同步方法的訪問,它的原理是:賦予該對象唯一一把'鑰匙',當多個線程進入對象,只有取得該對象鑰匙的線程才可以訪問同步方法,其它線程在該對象中等待,直到該線程用wait()方法放棄這把鑰匙,其它等待的線程搶佔該鑰匙,搶佔到鑰匙的線程後才可得以執行,而沒有取得鑰匙的線程仍被阻塞在該對象中等待。 file://聲明同步的一種方式:將方法聲明同步 class store { public synchonized void store_in() { …. } public synchonized void store_out(){ ….} } 2 利用wait()、notify()及notifyAll()方法發送訊息實現線程間的相互聯絡 Java程式中多個線程通過訊息來實現互動聯絡的,這幾種方法實現了線程間的訊息發送。例如定義一個對象的synchonized 方法,同一時刻只能夠有一個線程訪問該對象中的同步方法,其它線程被阻塞。通常可以用notify()或notifyAll()方法喚醒其它一個或所有線程。而使用wait()方法來使該線程處於阻塞狀態,等待其它的線程用notify()喚醒。 一個實際的例子就是生產和銷售,生產單元將產品生產出來放在倉庫中,銷售單元則從倉庫中提走產品,在這個過程中,銷售單元必須在倉庫中有產品時才能提貨;如果倉庫中沒有產品,則銷售單元必須等待。 程式中,假如我們定義一個倉庫類store,該類的執行個體對象就相當於倉庫,在store類中定義兩個成員方法:store_in(),用來類比產品製造者往倉庫中添加產品;strore_out()方法則用來類比銷售者從倉庫中取走產品。然後定義兩個線程類:customer類,其中的run()方法通過調用倉庫類中的store_out()從倉庫中取走產品,類比銷售者;另外一個線程類producer中的run()方法通過調用倉庫類中的store_in()方法向倉庫添加產品,類比產品製造者。在主類中建立並啟動線程,實現向倉庫中添加產品或取走產品。 如果倉庫類中的store_in() 和store_out()方法不聲明同步,這就是個一般的多線程,我們知道,一個程式中的多線程是交替執行的,運行也是無序的,這樣,就可能存在這樣的問題: 倉庫中沒有產品了,銷售者還在不斷光顧,而且還不停的在'取'產品,這在現實中是不可思義的,在程式中就表現為負值;如果將倉庫類中的stroe_in()和store_out()方法聲明同步,如上例所示:就控制了同一時刻只能有一個線程訪問倉庫對象中的同步方法;即一個生產類線程訪問被聲明為同步的store_in()方法時,其它線程將不能夠訪問對象中的store_out()同步方法,當然也不能訪問store_in()方法。必須等到該線程調用wait()方法放棄鑰匙,其它線程才有機會訪問同步方法。 這個原理實際中也很好理解,當生產者(producer)取得倉庫唯一的鑰匙,就向倉庫中添放產品,此時其它的銷售者(customer,可以是一個或多個)不可能取得鑰匙,只有當生產者添放產品結束,交還鑰匙並且通知銷售者,不同的銷售者根據取得鑰匙的先後與否決定是否可以進入倉庫中提走產品。 |