在 Java 的 JDK 開發包中,已經自帶了對多執行緒技術的支援,可以方便地進行多執行緒程式設計。
實現多執行緒程式設計的方式主要有兩種:一種是繼承 Thread 類,另一種是實現 Runnable 介面。下面詳細介紹這兩種具體實現方式。
繼承 Thread 類
在學習如何實現多執行緒前,先來看看 Thread 類的結構,如下:
public class Thread implements Runnable
從上面的原始碼可以發現,Thread 類實現了 Runnable 介面,它們之間具有多型關係。
其實,
使用繼承 Thread 類的方式實現多執行緒,最大的局限就是不支援多繼承,因為 Java 語言的特點就是單根繼承,所以為了支援多繼承,完全可以實現 Runnable 介面的方式,一邊實現一邊繼承。但用這兩種方式建立的執行緒在工作時的性質是一樣的,沒有本質的區別。
Thread 類有如下兩個常用構造方法:
-
public Thread(String threadName)
-
public Thread()
繼承 Thread 類實現執行緒的語法格式如下:
public class NewThreadName extends Thread
{ //NewThreadName 類繼承自 Thread 類
public void run()
{
//執行緒的執行程式碼在這裡
}
}
執行緒實現的業務程式碼需要放到 run() 方法中。當一個類繼承 Thread 類後,就可以在該類中覆蓋 run() 方法,將實現執行緒功能的程式碼寫入 run() 方法中,然後同時呼叫 Thread 類的 start() 方法執行執行緒,也就是呼叫 run() 方法。
Thread 物件需要一個任務來執行,任務是指執行緒在啟動時執行的工作,該工作的功能程式碼被寫在 run() 方法中。當執行一個執行緒程式時,就會自動產生一個執行緒,主方法正是在這個執行緒上執行的。當不再啟動其他執行緒時,該程式就為單執行緒程式。主方法執行緒啟動由 Java 虛擬機器負責,開發人員負責啟動自己的執行緒。
如下程式碼演示了如何啟動一個執行緒:
new NewThreadName().start(); //NewThreadName 為繼承自 Thread 的子類
注意:如果 start() 方法呼叫一個已經啟動的執行緒,系統將會丟擲 IllegalThreadStateException 異常。
例 1
編寫一個 Java 程式演示執行緒的基本使用方法。這裡建立的自定義執行緒類為 MyThread,此類繼承自 Thread,並在重寫的 run() 中輸出一行字串。
MyThread 類程式碼如下:
public class MyThread extends Thread
{
@Override
public void run()
{
super.run();
System.out.println("這是執行緒類 MyThread");
}
}
接下來編寫啟動 MyThread 執行緒的主方法,程式碼如下:
public static void main(String[] args)
{
MyThread mythread=new MyThread(); //建立一個執行緒類
mythread.start(); //開啟執行緒
System.out.println("執行結束!"); //在主執行緒中輸出一個字串
}
執行上面的程式將看到如下所示的執行效果。
執行結束!
這是執行緒類 MyThread
從上面的執行結果來看,MyThread 類中 run() 方法執行的時間要比主執行緒晚。這也說明
在使用多執行緒技術時,程式碼的執行結果與程式碼執行順序或呼叫順序是無關的。同時也驗證了執行緒是一個子任務,CPU 以不確定的方式,或者說以隨機的時間來呼叫執行緒中的 run() 方法,所以就會出現先列印“執行結束!”,後輸出“這是執行緒類MyThread”這樣的結果了。
例 2
上面介紹了執行緒的呼叫具有隨機性,為了更好地理解這種隨機性這裡編寫了一個案例進行演示。
(1) 首先建立自定義的執行緒類 MyThread01,程式碼如下:
package ch14;
public class MyThread01 extends Thread
{
@Override
public void run()
{
try
{
for(int i=0;i<10;i++)
{
int time=(int)(Math.random()*1000);
Thread.sleep(time);
System.out.println("當前執行緒名稱="+Thread.currentThread().getName());
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
(2) 接下來編寫主執行緒程式碼,在這裡除了啟動上面的 MyThread01 執行緒外,還實現了 MyThread01 執行緒相同的功能。主執行緒的程式碼如下:
package ch14;
public class Test02
{
public static void main(String[] args)
{
try
{
MyThread01 thread=new MyThread01();
thread.setName("myThread");
thread.start();
for (int i=0;i<10;i++)
{
int time=(int)(Math.random()*1000);
Thread.sleep(time);
System.out.println("主執行緒名稱="+Thread.currentThread().getName());
}
}
catch(InterruptedException e)
{
e.printStackTrace();
}
}
}
在上述程式碼中,為了展現出執行緒具有隨機特性,所以使用亂數的形式來使執行緒得到掛起的效果,從而表現出 CPU 執行哪個執行緒具有不確定性。
MyThread01 類中的 start() 方法通知“執行緒規劃器”此執行緒已經準備就緒,等待呼叫執行緒物件的 run() 方法。這個過程其實就是讓系統安排一個時間來呼叫 Thread 中的 run() 方法,也就是使執行緒得到執行,啟動執行緒,具有非同步執行的效果。
如果呼叫程式碼 thread.run() 就不是非同步執行了,而是同步,那麼此執行緒物件並不交給“執行緒規劃器”來進行處理,而是由 main 主執行緒來呼叫 run() 方法,也就是必須等 run() 方法中的程式碼執行完後才可以執行後面的程式碼。
這種採用亂數延時呼叫執行緒的方法又稱為非同步呼叫,程式執行的效果如下所示。
當前執行緒名稱=myThread
主執行緒名稱=main
當前執行緒名稱=myThread
當前執行緒名稱=myThread
當前執行緒名稱=myThread
主執行緒名稱=main
當前執行緒名稱=myThread
當前執行緒名稱=myThread
主執行緒名稱=main
當前執行緒名稱=myThread
主執行緒名稱=main
當前執行緒名稱=myThread
當前執行緒名稱=myThread
當前執行緒名稱=myThread
主執行緒名稱=main
主執行緒名稱=main
主執行緒名稱=main
主執行緒名稱=main
主執行緒名稱=main
主執行緒名稱=main
例 3
除了非同步呼叫之外,同步執行執行緒 start() 方法的順序不代表執行緒啟動的順序。下面建立一個案例演示同步執行緒的呼叫。
(1) 首先建立自定義的執行緒類 MyThread02,程式碼如下:
package ch14;
public class MyThread02 extends Thread
{
private int i;
public MyThread02(int i)
{
super();
this.i=i;
}
@Override
public void run()
{
System.out.println("當前數位:"+i);
}
}
(2) 接下來編寫主執行緒程式碼,在這裡建立 10 個執行緒類 MyThread02,並按順序依次呼叫它們的 start() 方法。主執行緒的程式碼如下:
package ch14;
public class Test03
{
public static void main(String[] args)
{
MyThread02 t11=new MyThread02(1);
MyThread02 t12=new MyThread02(2);
MyThread02 t13=new MyThread02(3);
MyThread02 t14=new MyThread02(4);
MyThread02 t15=new MyThread02(5);
MyThread02 t16=new MyThread02(6);
MyThread02 t17=new MyThread02(7);
MyThread02 t18=new MyThread02(8);
MyThread02 t19=new MyThread02(9);
MyThread02 t110=new MyThread02(10);
t11.start();
t12.start();
t13.start();
t14.start();
t15.start();
t16.start();
t17.start();
t18.start();
t19.start();
t110.start();
}
}
程式執行後的結果如下所示,從執行結果中可以看到,雖然呼叫時數位是有序的,但是由於執行緒執行的隨機性,導致輸出的數位是無序的,而且每次順序都不一樣。
當前數位:1
當前數位:3
當前數位:5
當前數位:7
當前數位:6
當前數位:2
當前數位:4
當前數位:8
當前數位:10
當前數位:9
實現 Runnable 介面
如果要建立的執行緒類已經有一個父類別,這時就不能再繼承 Thread 類,因為 Java 不支援多繼承,所以需要實現 Runnable 介面來應對這樣的情況。
實現 Runnable 介面的語法格式如下:
public class thread extends Object implements Runnable
提示:從 JDK 的 API 中可以發現,實質上 Thread 類實現了 Runnable 介面,其中的 run() 方法正是對 Runnable 介面中 run() 方法的具體實現。
實現 Runnable 介面的程式會建立一個 Thread 物件,並將 Runnable 物件與 Thread 物件相關聯。Thread 類有如下兩個與 Runnable 有關的構造方法:
-
public Thread(Runnable r);
-
public Thread(Runnable r,String name);
使用上述兩種構造方法之一均可以將 Runnable 物件與 Thread 範例相關聯。使用 Runnable 介面啟動執行緒的基本步驟如下。
-
建立一個 Runnable 物件。
-
使用引數帶 Runnable 物件的構造方法建立 Thread 範例。
-
呼叫 start() 方法啟動執行緒。
通過實現 Runnable 介面建立執行緒時開發人員首先需要編寫一個實現 Runnable 介面的類,然後範例化該類的物件,這樣就建立了 Runnable 物件。接下來使用相應的構造方法建立 Thread 範例。最後使用該範例呼叫 Thread 類的 start() 方法啟動執行緒,如圖 1 所示。
圖1 使用Runnable介面啟動執行緒流程