1. 程式人生 > 其它 >Java多執行緒_建立執行緒的多種方式

Java多執行緒_建立執行緒的多種方式


title: Java多執行緒_建立執行緒的多種方式
date: 2022-02-28 19:02:15
tags: Java


Java多執行緒_建立執行緒的多種方式

一、執行緒與程序

​ 幾乎所有的作業系統都支援程序的概念,所有執行中的任務都對應一個程序(Process)。當一個程式進入記憶體執行時,即變成一個程序。程序是處於執行中的程式,並且具有一定的獨立功能,程序是系統進行資源排程的一個獨立單位。

​ 一般而言,程序包含三個特徵:獨立性、動態性、併發性。

​ 一個程式執行後至少會有一個程序,一個程序可以包含多個執行緒,但至少要有個執行緒。

歸納起來就是說:作業系統可以同時執行多個任務,每個任務就是程序;程序可以同時執行多個任務,每個任務就是執行緒。

溫馨提示(併發性與並行性):

併發性(concurrency)和並行性(parallel)是兩個概念,並行是指在同一時刻,有多條指令在多個處理器上同時執行;併發是指在同一時刻只能有一條指令執行,但多個程序指令被快速輪換執行,使得在巨集觀上具有多個程序被同時執行的效果。

二、多執行緒的優勢

​ 執行緒在程式中是獨立的、併發的執行流,與分隔的程序相比,執行緒分隔程度要小很多。當作業系統建立一個程序時,必須為該程序分配獨立的記憶體空間,並分配大量的相關資源;但建立執行緒則簡單很多,因此使用多執行緒來實現併發比使用多程序實現併發要強很多。

​ 1、程序之間不能共享記憶體,但執行緒之間共享記憶體非常容易,

​ 2、系統建立程序時需要為該程序重新分配系統資源,但建立執行緒則代價要小很多,因此使用多執行緒實現多工併發要比程序效率高很多。

​ 3、Java語言內建了多執行緒功能支援,而不是單純的作為底層作業系統的排程模式,從而簡化了Java的多執行緒程式設計。

三、執行緒的建立和啟動

​ Java使用Thread類代表執行緒,所有的執行緒物件都必須是Thread類或其子類的例項,以下則介紹其中三種常見建立執行緒物件的方法。

1、繼承Thread類建立執行緒類

​ 建立步驟:

​ ①定義Thread類的子類,並重寫該類的run()方法,該run()方法體就代表了執行緒需要完成的任務。因此吧run()方法稱為執行緒執行體。

​ ②建立Thread子類的例項,即建立了執行緒物件。

​ ③呼叫執行緒物件的start()方法來啟動該執行緒。

/**
 * @program: code
 * @description: 通過繼承Thread類建立執行緒物件
 * @author: yangzhihong
 * @create: 2022-02-28 20:29
 **/
public class Thread_jc extends Thread {
    private int i;
    //重寫run()方法,run()方法的方法體就是執行緒執行體。
    public void run(){
        for (i = 0; i < 100; i++) {
            //d當執行緒類繼承Thread類時,直接使用this即可獲取當前執行緒
            //Thread物件的getName()返回當前執行緒的名字
            //因此可以直接呼叫getName()方法返回當前執行緒的名字
            System.out.println("我是子執行緒" + getName() + "" + i);
        }
    }
    public static void main(String[] args){
        for (int i = 0; i < 100; i++) {
            //呼叫Thread的currentThread()方法獲取當前執行緒
            System.out.println("我是主執行緒" + Thread.currentThread().getName() + "" + i);
            if (i == 20) {
                //建立並啟動第一個執行緒
                new Thread_jc().start();
                //建立並啟動第二個執行緒
                new Thread_jc().start();
            }
        }
    }
}

Thread類提供了常用的API

Thread.currentThread() : 靜態方法用於獲取當前的執行緒物件。

getName() : 可以獲取執行緒物件的名稱。

​ 主執行緒的名稱預設是main,不能修改。

​ 子執行緒的名稱預設是 Thread-0,.......

​ 可以修改,修改方式,可以提供有參構造方法,呼叫父類的有參構造方法Thread(String name)

​ 也可以使用setName(String name) 設定執行緒名稱。

2、實現Runnable介面建立執行緒

​ 自定義一個類實現Runnable介面,這個類是一個任務類,這個任務類需要重寫Runnable介面中的任務方法,這個類的物件是一個任務物件,如果這個任務物件的任務需要在子執行緒中執行,必須依賴執行緒物件。

​ 建立步驟:

​ ①定義Runnable介面的實現類,並重寫該方法的run()方法,該run()方法的方法體同樣是該執行緒的執行緒執行體。

​ ②建立Runnable實現類的例項,並以此作為Thread的target來建立Thread物件,該Thread物件才是真正的執行緒物件。

​ ③呼叫執行緒執行緒物件的start()方法來啟動執行緒。

​ **注意:Runnable物件僅僅作為Thread物件的target,Runnable實現類裡包含的run()方法僅作為執行緒執行體。而實際的執行緒物件依然是Thread例項,只是Thread執行緒負責執行其target的run()方法。4

/**
 * @program: code
 * @description: 實現Runnable介面建立執行緒物件
 * @author: yangzhihong
 * @create: 2022-03-01 10:24
 **/
// 自定義一個任務類
class MyTask implements Runnable {

    @Override
    public void run() {
        for(int i=1; i<=100; i++) {
            System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + i + "個小怪");
        }
    }
}

public class RunnableThread {

    public static void main(String[] args) {

        // 建立任務物件
        MyTask myTask = new MyTask();

        // 建立執行緒物件
        Thread thread = new Thread(myTask, "子執行緒");
        // 開啟子執行緒物件的任務
        thread.start();

        for(int j=1; j<=100; j++) {
            System.out.println(Thread.currentThread().getName() + "----" + "刷出第" + j + "個大BOSS");
        }
    }

}

3、使用Callable和Future建立執行緒

​ 從Java5 開始,Java提供了Callable介面,該介面與Runnable介面非常相似,Callable介面提供了一個call()方法可以作為執行緒執行體,但call()方法比run()方法功能更強大:可以有返回值、可以宣告丟擲異常。

​ Java5 提供了一個與Callable配套的Future介面,Future有個實現類FutureTask,該類的RunnableFuture介面對Future、Runnable都進行了實現。可以作為中介來關聯Runnable與Future,並通過Future的公共方法V get() 獲取到Callable的返回值,於是乎Callable物件就可以作為Thread的target來建立執行緒了。(V get()方法中的V是表示泛型,表示Callable接口裡的型別必須與call()返回值型別相同)

​ 建立步驟:

​ ①建立Callable介面的實現類,並實現call()方法,該call()方法將作為執行緒執行體,並且該call()方法有返回值,再建立Callable實現類的例項物件。從Java8 開始可以直接使用Lambda表示式建立Callable物件。

​ ②使用FutureTask類包裝Callable物件,該FutureTask物件封裝了該Callable物件的call()方法的返回值。

​ ③使用FutureTask物件作為Thread物件的target建立並啟動新執行緒。

​ ④呼叫FutureTask物件的get()方法來獲得子執行緒執行結束後的返回值。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * @program: code
 * @description: 通過Callable與Future來建立執行緒物件
 * @author: yangzhihong
 * @create: 2022-03-01 11:56
 **/
// 實現了Callable介面的類,也是一個任務類
class MyCall implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {

        int sum = 0;

        for(int i=1; i<=100; i++) {
            sum += i;
        }

        return sum;
    }
}

public class CallableFutureThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        // 建立任務物件
        MyCall myCall = new MyCall();
        // 建立FutureTask物件這個物件實現了RunnableFuture介面
        // RunnableFuture多繼承了Runnable介面和Future介面
        // 在構造FutureTask物件時,可以通過構造方法將Callable介面的
        // 實現物件傳入
        FutureTask<Integer> futureTask = new FutureTask<>(myCall);
        // 建立一個執行緒物件
        Thread thread = new Thread(futureTask);
        thread.start();
        // 通過get()方法來獲取Callable任務方法的返回結果
        if(futureTask.isDone()) {
            Integer result = futureTask.get();
            System.out.println(result);
        }

    }

}

4、這三種建立執行緒方式的對比

​ 通過繼承Thread類和實現Runnable、Callable介面都可以實現多執行緒,不過實現Runnable介面與實現Callable介面的方式基本相同,只是Callable接口裡定義的方法有返回值和可以宣告丟擲異常,因此可以將Runnable和Callable歸為同一種實現介面方式。

實現介面方式建立執行緒優缺點:

​ ①執行緒類只是實現了Runnable介面或Callable介面,還可以繼承其他類。

​ ②在這種方式下,多個執行緒可以共享同一個target物件,所以非常適合多個相同執行緒來處理同一份資源,從而可以將CPU、程式碼和資料分開。形成清晰的模型。較好的體現了面向物件的思想。

​ ③缺點,程式設計稍微複雜一些,如果需要訪問當前執行緒,則必須使用Thread.currentThread()方法。

採用繼承Thread類方式建立執行緒優缺點:

​ ①缺點,因為執行緒類已經繼承了Thread類,所以不能再繼承其他父類。

​ ②優點,編寫簡單,如果需要訪問當前執行緒,則不需要使用Thread.currentThread()方法,直接this即可獲得當前執行緒。

因此一般推薦採用實現Runnable介面、Callable介面方式來建立多執行緒。