1. 程式人生 > >Java多執行緒的實現(程序與執行緒的概念、Java繼承Thread類實現多執行緒、Java實現Runnable介面實現多執行緒、Thread與Runnable的區別、實現Callable介面實現多執行緒)

Java多執行緒的實現(程序與執行緒的概念、Java繼承Thread類實現多執行緒、Java實現Runnable介面實現多執行緒、Thread與Runnable的區別、實現Callable介面實現多執行緒)

1 程序與執行緒

1.1 程序與執行緒的概念

什麼是程序?   程序: 作業系統中一個程式的執行週期。(比如我們想要在電腦上登入QQ,從雙擊qq按鈕---->關閉qq這個過程就是一個程序)   多程序: 同一時刻跑多個程式。   在DOS(磁碟作業系統時代),由於其本身就是一個單程序的作業系統,所以在同一時間段上只能夠有一個程式執行;後來發展到Windows系統後,我們可以發現多個程式可以同時執行(比如我們在用騰訊視訊看電視的時候照樣可以登入qq和朋友聊天),所以Window是一個多程序的作業系統。 什麼是執行緒?   一個程式(程序)可以同時執行多個任務,通常每一個任務就稱為一個執行緒。(比如登入QQ,與朋友聊天,qq程序寫執行緒和讀執行緒就在同時執行,一個瀏覽器可以同時下載多個圖片、音樂等等)

1.2 程序&執行緒

  • 與程序相比,執行緒更加的輕量級,建立、撤銷一個執行緒比啟動、撤銷一個程序開銷要小的多,一個程序中的所有執行緒共享此程式(程序)的所有資源。
  • 沒有程序就沒有執行緒,程序一旦終止,其內的執行緒也將不復存在(將像你登入QQ和朋友聊天,如果某一時刻你關掉了QQ,讀執行緒和寫執行緒也將不復存在)。
  • 程序是作業系統資源排程的基本單位,程序可以獨享資源,執行緒需要依託程序提供的資源,無法獨立申請作業系統資源,是作業系統任務執行的基本單位。

多程序與多執行緒的區別?   本質區別在於,每個程序擁有自己的一整套變數,而執行緒則共享資料,共享變數使得執行緒之間的通訊比程序之間的通訊更加有效方便。

2 Java多執行緒實現

2.1 繼承Thread類實現多執行緒

  java.long.Thread是一個執行緒操作的核心類,(java.long包會自動匯入)新建一個執行緒最簡單的方式就是直接繼承Thread類,而後覆寫該類中的run()方法。(run方法是執行緒入口,就相當於主執行緒的main方法,main方法是主執行緒的入口) 繼承Thread定義執行緒:

class MyThread extends Thread{
    private String title;
    public myThread(String title) {
        this.title = title;
} @Override public void run() //所有執行緒從此處開始執行 { for(int i=0;i<10;i++) { System.out.println(this.title+" i="+i); } } }

  新建一個執行緒最簡單的方式就是繼承Thread類,而後覆寫該類中的run()方法,很自然我們就想到產生執行緒類的例項化物件而後呼叫run()方法。 在這裡插入圖片描述   顯然,這個時候只做了順序列印,和多執行緒一點關係都麼有(多執行緒的意思是交替執行不可能順序列印)可見啟動多執行緒的方式錯誤。 啟動多執行緒:public synchronized void start() 此方法會自動呼叫執行緒的run()方法 在這裡插入圖片描述   無論哪種方式實現多執行緒,執行緒啟動一定呼叫Thread類的start()方法,而不是直接物件名.run()。 先看start()的原始碼: 在這裡插入圖片描述

  1. 首先我們看到在start()方法中丟擲IllegalThreadStateException異常,按照原有處理方式,應當在呼叫處進行異常處理,然而此處沒有處理也不會報錯,因此是一個RunTimeException,這個異常的產生只是因為你重複啟動了執行緒才會產生,所以每一個執行緒物件只能夠啟動一次在這裡插入圖片描述 問題:為什麼要通過start()方法來呼叫run()方法,而不是直接run()執行?
  2. 我們看到在start()方法中呼叫了start0()方法,而這個方法是一個只宣告而未實現的方法,同時使用native關鍵字進行定義。private native void start0(); native指的是呼叫本機的原生系統函式。 Thread類有個 registerNatives 本地方法,該方法主要的作用就是註冊一些本地方法供 Thread 類使用,如 start0(),stop0() 等等,可以說,所有操作本地執行緒的本地方法都是由它註冊的。這個方法放在一個 static 語句塊中,當該類被載入到 JVM 中的時候,它就會被呼叫,進而註冊相應的本地方法。而本地方法 registerNatives 是定義在 Thread.c 檔案中的。Thread.c 是個很小的檔案,它定義了各個作業系統平臺都要用到的關於執行緒的公用資料和操作,如下: 在這裡插入圖片描述   觀察上邊一小段程式碼,可以容易的看出 Java 執行緒呼叫 start->start0 的方法,實際上會呼叫到 JVM_StartThread 方法,那這個方法又是怎麼處理的呢? 在這裡插入圖片描述   綜上可知,Java執行緒建立的呼叫流程為::呼叫Thread類的start()(Java方法)方法,判斷執行緒是不是新建立的,如果不是新建立的丟擲IllegalThreadStateException異常,如果是新建立的呼叫start0()(JVM)方法,JVM進行資源排程,系統分配,準備好了之後回撥run()(Java方法)方法,執行執行緒的具體操作任務。   如果直接呼叫run()方法,JVM並沒有進行執行緒的資源排程與系統分配,物件名.run()只是個普通方法去執行相應的操作而已。

2.2 實現Runnable介面來實現多執行緒

在這裡插入圖片描述   Thread()的核心功能是進行執行緒的啟動。如果一個類為了實現多執行緒直接去繼承Thread類就會有單繼承侷限,在Java中又提供有另外一種實現模式:Runnable介面 實現Runnable定義執行緒

class myThread implements Runnable{
    private String title;
    public myThread(String title) {
        this.title = title;
    }
    @Override
    public void run()  //所有執行緒從此處開始執行
    {
        for(int i=0;i<10;i++)
        {
            System.out.println(this.title+" i="+i);
        }
    }
}

  此時的myThread類繼承的不在是Thread類而是實現了Runnable介面,雖然解決了單繼承侷限問題,但是沒有start()方法了,執行緒無法啟動。   那麼此時就需要關注Thread類提供的構造方法。 Thread類提供的構造方法:public Thread(Runnable target)可以接受Runnable介面物件。 在這裡插入圖片描述 對於此時的Runnable介面物件可以採用匿名內部類或者Lambda表示式來定義 範例:使用匿名內部類進行Runnable物件建立

package  www.miao.java;
public class Test{
    public static void main(String [] args){
        //匿名內部類
        new Thread ( new Runnable(){
            @Override
            public void run() {
                System.out.println("Hello World!!!");
            }
        } ).start();
    }
 }

範例:使用Lamdba表示式進行Runnable物件建立

package  www.miao.java;
public class Test{
    public static void main(String [] args){
        Runnable runnable = ()-> System.out.println("Hello World!!");
        new Thread(runnable).start();
    }
 }

在實際開發之中,大多采用的是以上兩種操作來進行Runnable物件建立。

2.3 繼承Thread類與實現Runnable介面的區別

  • 從使用形式上講,明顯使用Runnable實現多執行緒要比繼承Thread類好,因為可以避免單繼承侷限。
  • Thread類的定義:public class Thread implements Runnable Thread類是Runnable介面的子類,那麼Thread類一定覆寫了Runnable介面的run()方法。
@Override
public void run() {
	if (target != null) {
		target.run();		
	}
}
public Thread(Runnable target) {
	init(null, target, "Thread-" + nextThreadNum(), 0);
}
  • Thread與自定義的執行緒類(實現了Runnable介面)是一個典型的代人模式,Thread類負責輔助真實業務操作(資源排程、啟動執行緒)自定義執行緒類主要負責真實業務的實現。
  • 使用Runnable介面實現的多執行緒程式類可以更好的描述共享的概念

例如:使用Thread實現資料共享

package  www.miao.java;
class MyThread extends  Thread{
    private int ticket = 10;
    private String title;
    public MyThread( String title) {
        this.title = title;
    }

    @Override
    public void run() {
        while(this.ticket>0)
        {
            System.out.println(this.title +"剩下"+this.ticket--+"票!");
        }
    }
}
public class Test{
    public static void main(String [] args){
       MyThread thread1 = new MyThread("12306");
       MyThread thread2 = new MyThread("攜程");
       MyThread thread3 = new MyThread("火車站");
       thread1.start();
       thread2.start();
       thread3.start();
    }
 }

在這裡插入圖片描述   此時啟動三個執行緒實現賣票處理,結果變為了賣各自的票。即12306、攜程、火車站各有十張票,但是現實情況卻是12306、攜程、火車站櫃檯它們一起賣這十張票. 例如:使用Runnable介面實現資料共享

package  www.miao.java;
class MyThread implements  Runnable{
    private int ticket = 10;

    @Override
    public void run() {
        while(this.ticket>0)
        {
            System.out.println("剩下"+this.ticket--+"票!");
        }
    }
}
public class Test{
    public static void main(String [] args){
       MyThread thread1 = new MyThread();
       new Thread(thread1).start();
       new Thread(thread1).start();

    }
 }

在這裡插入圖片描述

1.4 實現Callable介面實現多執行緒

  從JDK1.5開始追加了新的開發包,java.uti.concurrent這個開發包主要是進行高併發程式設計使用的,包含很多在高併發操作中會使用的類,在這個包裡定義有一個新的介面Callable。 在這裡插入圖片描述   Runnable中的run()方法沒有返回值,執行緒一開始就不回頭,但是很多時候需要一些返回值,例如某些執行緒執行完成後可能帶來一些返回結果,這種情況下只能利用Callable來實現多執行緒。 實現Callable介面定義執行緒:

class MyThread implements Callable<String>
{
    private int ticket =10;

    @Override
    public String call() {
        while (this.ticket > 0) {
            System.out.println("剩餘票數:" + this.ticket--);
        }
        return "票賣完了,下次吧!";
    }
}

  此時的執行緒不僅沒有單繼承侷限,還有返回值,但是沒有Thread類的start()方法,執行緒無法啟動。 在這裡插入圖片描述   RunnableFuture介面實現了Runnable、Future介面,FutureTask類實現了RunnableFuture介面,所以FutureTask覆寫了RunnableFuture介面的所有方法,包括Runnable介面中的Run()方法,以及Future中的get()方法。且FutureTask中有構造方法可以接收Callable物件。   啟動執行緒:

package  www.miao.java;
import java.util.concurrent.*;
class MyThread implements Callable<String>
{
    private int ticket =10;

    @Override
    public String call(){
        while (this.ticket > 0) {
            System.out.println("剩餘票數:" + this.ticket--);
        }
        return "票賣完了,下次吧!";
    }
}

public class Test{
    public static void main(String [] args)throws InterruptedException,ExecutionException{
        FutureTask<String> task = new FutureTask<>(new MyThread());
        new Thread(task).start();
        new Thread(task).start();
        System.out.println(task.get());
    }
 }

在這裡插入圖片描述