1. 程式人生 > >Java併發:建立執行緒的兩種方法:繼承Thread類和實現Runnable介面(一)

Java併發:建立執行緒的兩種方法:繼承Thread類和實現Runnable介面(一)

【1】瞭解程序與執行緒的概念

  • 程序是一個程式在其自身的地址空間中執行一次活動,是資源申請、排程和獨立執行的單位。
  • 執行緒是程序中的一個單一的連續控制流程,一個程序可以包含一個或多個執行緒。
  • 如果要在一個程式中實現多段程式碼同時交替執行,就需要產生多個執行緒,並指定每個執行緒上要執行的程式程式碼段,這就是多執行緒。
  • 當程式執行時,就自動產生了一個執行緒,主函式main就是在這個執行緒上執行的,當不再產生新的執行緒時,程式就是單執行緒的。例如,在的部落格中本文——“JAVA——package語句、class環境變數配置”中的程式就是單執行緒的。

【2】實現多執行緒的兩種方法

  • 當啟動程式時就自動產生了一個執行緒,主函式main就是在這個執行緒上執行的。當不再產生新的執行緒時,程式就是單執行緒的。
  • 通過查閱JDK文件的Thread類,可以發現建立多執行緒有兩種方法:繼承Thread類、實現Runable介面。
    這裡寫圖片描述
    翻譯:每個執行緒都有優先順序。優先順序較高的執行緒優先執行具有較低優先順序的執行緒。每個執行緒可能也可能被標記為守護程序。當代碼執行在某個執行緒中建立一個新的執行緒物件時,新執行緒的優先順序初始設定為建立執行緒的優先順序,當且僅當建立執行緒為守護程序時,該執行緒是一個守護執行緒。
  • JAVA中的執行緒是通過java.lang.Thread類來控制的,一個Thread類的物件代表一個執行緒,且只能代表一個執行緒,通過Thread類和它定義的物件,可以獲取當前執行緒物件、獲取某一執行緒的名稱,可以實現控制執行緒暫停一段時間等功能。

  • 第一種建立執行緒的方法:繼承Thread類
    兩種方法的示例為:例如,一個計算大於給定值的素數的執行緒可以寫成如下。
    這裡寫圖片描述

  • 方法步驟總結:
    (1)定義一個類(如PrimeThread)繼承Thread;
    (2)重寫Thread類中的run方法,將需要被多執行緒執行的程式碼儲存到該run方法當中。
    (3)建立Thread類的子類建立執行緒物件。
    (4)直接呼叫子類從Thread類繼承的start方法,開啟一個執行緒(呼叫該執行緒的run方法)。

  • 第二種建立執行緒的方法:實現Runable介面

    這裡寫圖片描述

  • Thread類有一個Thread(Runnable target)構造方法,在Runable介面類中只有一個run()方法。當使用Thread(Runnable target)方法建立執行緒物件時,需要為該方法傳遞一個實現 Runnable介面的物件,這樣建立的執行緒將呼叫那個實現了Runnable介面類物件中的run()方法作為其執行程式碼,而不再是呼叫Thread類中的run方法了。

  • 方法步驟總結:
    (1)定義一個類(如PrimeRun)實現Runnable介面,覆蓋Runnable介面中的run方法,將執行緒要執行的程式碼存放在該run方法中;
    (2)通過Thread類建立執行緒物件,將Runnable介面的子類例項物件作為實際引數傳遞給Thread類的構造方法。

  • 兩種方式區別:

    (1)繼承Thread: 執行緒程式碼存放Thread子類run方法中,且該run方法被呼叫。
    (2)實現Runnable:執行緒程式碼存在實現了Runnable類介面的物件的run方法中,且該run方法被呼叫。

  • Thread類中的run方法(函式)
    這裡寫圖片描述

  • 要實現多執行緒,就得編寫一個繼承了Thread類的子類,要將一段程式碼在一個新的執行緒上執行,該程式碼應該在一個類的run函式中,並且run函式所在的類是Thread類的子類。從JDK文件可知,Thread類的子類應該覆蓋run方法(函式)。

  • 啟動一個新的執行緒,不是直接呼叫Thread子類的物件的run方法,而是呼叫Thread子類物件的start方法,start方法是從Thread類中繼承的方法,Thread類物件的start方法將產生一個新的執行緒,並在該執行緒上執行該Thread類物件中的run方法。根據面向物件的多型性可知,在該執行緒上實際執行的是我們編寫的那個類(Thread的子類)物件中的run方法。
  • 由於執行緒的程式碼段在run方法中,那麼該方法執行完以後,執行緒也就相應的結束了,因而可以通過控制run方法中的迴圈條件來控制執行緒的終止。這個問題就是後面會講到的“執行緒生命的控制”問題。

【2-1】直接繼承Thread

  • 首先,為了確保類名的唯一性,在以下程式碼使用包(package)將類組織起來。
/*程式碼1*/
package mythread;
class TestThread extends Thread
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
public class ThreadDemo
{
    public static void main(String[] args) 
    {
        new TestThread().start();
        while(true)
        {
            System.out.println("main thread is running");
        }
    }
}
  • Thread.currentThread()靜態函式獲得程式碼當前執行時對應的那個執行緒物件。得到當前執行緒物件後,呼叫getName()方法,取出當前執行緒的名稱字串。

  • 以上程式碼1中,沒有直接呼叫TestThread類物件的run方法,而是呼叫了TestThread類物件從Thread類繼承來的start方法。

  • 編譯、執行(可使用 Ctrl+C 終止程式執行):
 javac -d . ThreadDemo.java
 java mythread.ThreadDemo

這裡寫圖片描述

  • 可以發現,兩個while迴圈處的程式碼同時交替執行。main函式呼叫TestThread.start()方法啟動了TestThread.run()函式後,main函式不等待TestThread.run()函式返回就繼續執行(即main中的while語句繼續執行)。TestThread.run()函式一邊獨自執行,不影響原來的main函式的執行。但是,TestThread.run()函式和main中的while語句是否在同一時刻執行的,眾說紛紜,希望大神們指教!
  • 有這麼一個說法:多核CPU在同一時刻可以執行多個執行緒,每個單核CPU每一時刻只能執行一個執行緒。在單核CPU中,多執行緒是通過時間片輪換技術執行的,每一時間段被一個執行緒所獨佔,只是佔用的時間很短,短到我們感覺是在同一時刻。

【2-2】實現Runable介面

/*程式碼2*/
package myrunnable;
class TestThread implements Runnable
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName()+" is running");
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();//等價於Thread t = new Thread(tDemo); t.start();

        while(true)
        {
            System.out.println("main thread is running");
        }

    }
}
  • 編譯、執行:
javac -d . ThreadDemo1.java
java myrunnable.ThreadDemo1
  • 可以發現,兩個while迴圈處的程式碼同時交替執行,實現了多執行緒。
    這裡寫圖片描述

【3】兩種方法的對比分析

  • 修改“程式1”:同一個執行緒物件同時呼叫start函式四次(啟動四個執行緒)
package mythread;
class TestThread extends Thread
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName() + " is running");
        }
    }
}
public class ThreadDemo
{
    public static void main(String[] args) 
    {
        TestThread t = new TestThread();
        t.start();
        t.start();
        t.start();
        t.start();

        while(true)
        {
            System.out.println("main thread is running");
        }
    }
}
  • 編譯、執行,可以發現,一個執行緒物件只能啟動一個執行緒。本例只是成功的啟動了一個執行緒,無論呼叫多少遍start()方法,結果都只有一個執行緒。
    這裡寫圖片描述
  • 修改“程式2”:同一個執行緒物件同時呼叫start函式四次(啟動四個執行緒)
package myrunnable;
class TestThread implements Runnable
{
    public void run()
    {
        while(true)
        {
            System.out.println(Thread.currentThread().getName()+" is running");
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
    }
}
  • 編譯、執行,可以發現,成功的啟動了四個執行緒。
    這裡寫圖片描述

這裡寫圖片描述

【3-1】實現Runnable介面方法實現多執行緒分析

  • 以張孝祥所編著的《Java就業培訓教程》的一個例子為例:某一趟列車有10張坐票,通過4個視窗售票。這樣,對於每一張坐票,要保證每一個視窗的售票不能將其重複售出。
/*程式碼3*/
package myrunnable;
class TestThread implements Runnable
{
    private int Numbe = 10;
    public void run()
    {
        while(true)
        {
            if (Numbe > 0)
            {
                try
                {
                    Thread.sleep(10);
                }
                catch (Exception e)
                {
                    System.out.println(e.getMessage());
                }
                System.out.println(Thread.currentThread().getName() +" is saling ticket "+ Numbe--);
            }
        }
    }
}
public class ThreadDemo1
{
    public static void main(String[] args)
    {
        TestThread tDemo = new TestThread();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
        new Thread(tDemo).start();
    }
}
  • 編譯、執行。可以發現,四個視窗把10張票給出售了,而且沒有出現將某一張票給重複出售。但是,除了原來的10張票,系統還可以多出售序號為-1和0的兩張票。這在實際情況下,是絕對不可行的。
    這裡寫圖片描述

  • 這種意外的問題就是“執行緒安全”問題。那麼,要解決以上安全問題,這就涉及到:如何實現執行緒間的同步問題。