C語言中文網 目錄

Java多線程的實現方式

Java 的 JDK 開發包中,已經自帶了對多線程技術的支持,可以方便地進行多線程編程。實現多線程編程的方式主要有兩種:一種是繼承 Thread 類,另一種是實現 Runnable 接口。下面詳細介紹這兩種具體實現方式。

繼承 Thread 類

在學習如何實現多線程前,先來看看 Thread 類的結構,如下:
public class Thread implements Runnable

從上面的源代碼可以發現,Thread 類實現了 Runnable 接口,它們之間具有多態關系。

其實,使用繼承 Thread 類的方式實現多線程,最大的局限就是不支持多繼承,因為 Java 語言的特點就是單根繼承,所以為了支持多繼承,完全可以實現 Runnable 接口的方式,一邊實現一邊繼承。但用這兩種方式創建的線程在工作時的性質是一樣的,沒有本質的區別。

Thread 類有如下兩個常用構造方法:
  1. public Thread(String threadName)
  2. 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 有關的構造方法:
  1. public Thread(Runnable r);
  2. public Thread(Runnable r,String name);

使用上述兩種構造方法之一均可以將 Runnable 對象與 Thread 實例相關聯。使用 Runnable 接口啟動線程的基本步驟如下。
  1. 創建一個 Runnable 對象。
  2. 使用參數帶 Runnable 對象的構造方法創建 Thread 實例。
  3. 調用 start() 方法啟動線程。

通過實現 Runnable 接口創建線程時開發人員首先需要編寫一個實現 Runnable 接口的類,然后實例化該類的對象,這樣就創建了 Runnable 對象。接下來使用相應的構造方法創建 Thread 實例。最后使用該實例調用 Thread 類的 start() 方法啟動線程,如圖 1 所示。


圖1 使用Runnable接口啟動線程流程

例 4

編寫一個簡單的案例演示如何實現 Runnable 接口,以及如何啟動線程。

(1) 首先創建一個自定義的 MyRmmable 類,讓該類實現 Runnable 接口,并在 run() 方法中輸出一個字符串。代碼如下:
package ch14;
public class MyRunnable implements Runnable
{
    @Override 
    public void run()
    { 
        System.out.println("MyRunnable運行中!"); 
    }
}

(2) 接下來在主線程中編寫代碼,創建一個 MyRunnable 類實例,并將該實例作為參數傳遞給 Thread 類的構造方法,最后調用 Thread 類的 start() 方法啟動線程。具體實現代碼如下:
package ch14;
public class Test04
{
    public static void main(String[] args)
    {
        Runnable runnable=new MyRunnable();
        Thread thread=new Thread(runnable);
        thread.start();
        System.out.println("主線程運行結束!");
    }
}

如上述代碼所示,啟動線程的方法非常簡單。運行結果如下所示,同樣驗證了線程執行的隨機性。
主線程運行結束!
MyRunnable運行中!

注意:要啟動一個新的線程,不是直接調用 Thread  子類對象的 run() 方法,而是調用 Thread 子類的 start() 方法。Thread 類的 start() 方法會產生一個新的線程,該線程用于執行 Thread 子類的 run() 方法。

兩種方法的比較

雖然 Thread 類和 Runnable 接口都可以創建線程,但是它們也都有各自的優缺點。

1. 繼承 Thread 類的優缺點

當一個 run() 方法體現在繼承 Thread 類中時,可以用 this 指向實際控制運行的 Thread 實例。因此,代碼不需要使用以下控制語句:
Thread.currenThread().sleep();

不再使用上面的控制語句,而是可以簡單地使用 Threadsleep() 方法,繼承 Thread 類的方式使代碼變得簡單易讀。

2. 實現 Runnable 接口的優缺點

從面向對象的角度來看,Thread 類是一個虛擬處理機嚴格的封裝,因此只有當處理機模型修改或擴展時,才應該繼承該類。由于 Java 技術只允許單一繼承,因此如果已經繼承了 Thread 類,就不能再繼承其他任何類,這會使用戶只能采用實現 Runnable 接口的方式創建線程。

精美而實用的網站,提供C語言、C++、STL、Linux、Shell、Java、Go語言等教程,以及socket、GCC、vi、Swing、設計模式、JSP等專題。

Copyright ?2011-2018 biancheng.net, 陜ICP備15000209號

底部Logo
极速pk10开户