多執行緒程式設計1-定義理解與三種實現方式

xbhog發表於2021-04-05

多執行緒程式設計

程式與執行緒的理解:

程式: 是程式的一次動態執行過程,它經歷了從程式碼載入、執行到執行完畢的一個完整過程,這個過程就是程式產生、發展到最終消亡的過程;

多程式: 作業系統能同時執行多個程式(程式),由於CPU具備分時機制,在每個程式都能迴圈獲得自己的CPU時間片;由於CPU執行的速度非常快,使得所有的程式好像是在同時執行一樣。

image-20210402215645884

程式與執行緒的區別與聯絡:

  1. 程式是資源排程的基本單位,執行一個可執行程式會建立一個或多個執行緒,程式就是執行起來的可執行程式;
  2. 執行緒是程式執行的基本單位,是輕量級的程式,每個程式中都有唯一的主執行緒,且只能有一個,主執行緒和程式是相互依存的關係,主執行緒結束,程式也會結束。

image-20210402220317818

具體例項(word):

每次啟動Word對於作業系統而言就相當於啟動了一個系統的程式,而在這個程式之上又有許多其他程式在執行(拼寫檢查等),那麼對於這些程式就是一個個多執行緒。如果Word關閉了,則這些拼寫檢查的執行緒也肯定會消失,但是如果拼寫檢查的執行緒消失了,並不一定會讓Word的程式消失;

多插一句:如果開啟兩個word文件,則表示當前作業系統建立了兩個程式。

多執行緒實現:

實現多執行緒需要一個執行緒的主體類,這個類可以繼承Thread、實現Runnable以及Callable介面完成定義;

Thread實現多執行緒:

繼承結構如下:

public class Thread extends Object implements Runnable

實現介面Runnable,所以必須實現介面中的抽象方法:

Modifier and Type Method Description
void run() 當一個實現介面Runnable的物件被用來建立執行緒時,啟動執行緒會導致物件的run方法在單獨執行的執行緒中被呼叫。
void start() 使執行緒開始執行;Java虛擬機器呼叫這個執行緒的run方法。

當產生多個物件時,這些物件就會併發的執行run()方法中的程式碼;

雖然多執行緒的執行方法都在run()方法中定義,但是在實際進行多執行緒啟動時並不能直接呼叫此方法,由於多執行緒時需要併發執行的,所以需要通過作業系統的資源排程才能執行,這樣多執行緒的啟動就必須利用Thread類中的start()方法完成,呼叫此方法會間接的呼叫run()方法。

例項:

package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
class MyThread extends Thread{  //單繼承
    private String title;
    public MyThread(String title){
        this.title = title;
    }
    //覆寫執行緒的run方法
    @Override
    public void run() {
        for (int i = 0 ; i < 10; i++){
            System.out.println(this.title+"執行,i =" +i);
        }
    }
}
public class Main{
    public static void main(String[] args){
        new MyThread("執行緒A").start();   //例項化執行緒物件並啟動
		new MyThread("執行緒B").start();
        new MyThread("執行緒C").start();
        
        //對照
        /*沒有開啟多執行緒*/
        new MyThread("執行緒A").run();
        new MyThread("執行緒B").run();
        new MyThread("執行緒C").run();
    }
}

由效果圖可以看出,三個執行緒在交替執行:

image-20210403225445774

假如面試題:

為什麼執行緒啟動的時候必須呼叫start()方法而不是直接呼叫run()方法?

在本程式中,程式呼叫了Thread類繼承而來的start()方法後,實際上他執行的還是覆寫後的run()方法,那為什麼不直接呼叫run()?

簡單的說下:是因為多執行緒需要呼叫作業系統的資源,在start()下有一個關鍵的部分start0()方法,並且在start0()方法上使用了navite關鍵字定義;

public synchronized void start() {
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
private native void start0(); //navite

什麼是navite?

navite是指:Java本機介面(Java Native Interface)簡稱:JNI;使用Java呼叫本機作業系統的函式功能完成一些特殊操作;

在Java中將start0()方法體交給JVM進行實現,所以這樣就會出現在windows或者在Linux中實現的start0()的是不同,不關係過程,只關心結果(是否呼叫了本機的作業系統的函式);

start0()作用:交由JVM進行匹配不同的作業系統,實現start0()方法體,功能:實現本機函式的呼叫;

具體百度、Google吧。

Runnable介面實現多執行緒:

出現的原因:為了解決Thread實現多執行緒出現的單繼承問題;並且增加了函式式介面;

Modifier and Type Method Description
void run() 當一個實現介面Runnable的物件被用來建立執行緒時,啟動執行緒會導致物件的run方法在單獨執行的執行緒中被呼叫。

實現程式碼:

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);
        }
    }
}

啟動方式一:

Thread threadA = new Thread(new MyThread("執行緒A"));
Thread threadB = new Thread(new MyThread("執行緒B"));
Thread threadC = new Thread(new MyThread("執行緒C"));
Thread threadD = new Thread(new MyThread("執行緒D"));
Thread threadE = new Thread(new MyThread("執行緒E"));

threadA.start();
threadB.start();
threadC.start();
threadD.start();
threadE.start();

啟動方式二:

//通過Lambal表示式定義執行緒主體
for(int x = 0;  x < 3;x++){
    String title = "執行緒物件-"+x;
    //實際上Thread傳入的型別是Runnable
    new Thread(()->{  //Lambda實現執行緒主體
        for(int y = 0; y < 20; y++){
            System.out.println(title+"執行,y"+y);
        }
    }).start();
}

Thread與Runnable的聯絡:

繼承結構:

public class Thread extends Object implements Runnable

在之前繼承Thread類的時候實際上覆寫的還是Runnable介面的run()方法。

實現併發訪問資源:

package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
class MyThreadConcurrent implements Runnable {
    private int ticket = 5;

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            //同步操作--》從5-1票數
            /*synchronized(this){
                if(this.ticket > 0){
                    System.out.println("賣票,ticket = "+this.ticket--);
                }
            }*/
            //票數亂數
            if(this.ticket > 0){
                System.out.println("賣票,ticket = "+this.ticket--);
            }

        }
    }
}
public class 併發資源訪問 {
    public static void main(String[] args) {
        MyThreadConcurrent thread = new MyThreadConcurrent();
        new Thread(thread).start();  //第一個執行緒
        new Thread(thread).start();  //第二個執行緒
        new Thread(thread).start();  //第三個執行緒
    }
}

總結一句話:Thread有單繼承的侷限性以及在有些情況下結構的不合理性;所以後面多執行緒的實現使用的都是Runnable介面。

Callable介面實現多執行緒:

為什麼要使用Callable介面來實現多執行緒?

因為使用Callable介面實現彌補了Runnable實現多執行緒沒有返回值的問題。

繼承結構如下:

@FunctionalInterface
public interface Callable<V>{
	public V call() throws Exception{
        
    }
}

定義的時候可以設定一個泛型,此泛型的型別就是call()方法的返回的資料型別,好處:可以避免向下轉型的安全隱患。

執行緒類主體完成後,需要啟動多執行緒的話還是需要通過Thread類實現的,又因為我們的Callable介面與Thread沒有聯絡,所以我們需要FutureTask類實現兩者之間的聯絡;如圖所示:

image-20210404234544335

通過FutureTask類繼承結構可以發現它是Runnable介面的子類;

程式碼實現如下:

package Java從入門到專案實戰.多執行緒程式設計.Java多執行緒實現;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class CallableThread implements Callable<String> {
    @Override
    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("執行緒執行 x = "+i);
        }
        return "xbhog";
    }
}

public class Callable介面實現多執行緒 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //將Callable例項化包裝在FutureTask類中,這樣就可以與Runnable介面關聯
        FutureTask<String> task = new FutureTask<String>(new CallableThread());
        //執行緒啟動
        new Thread(task).start();
        //獲取call()的返回值
        System.out.println("【執行緒返回資料】:"+task.get());
    }
}

相關文章