【多執行緒與高併發】- 執行緒基礎與狀態

怒放吧德德發表於2023-02-16

執行緒基礎與狀態

?生命不息,寫作不止
? 繼續踏上學習之路,學之分享筆記
? 總有一天我也能像各位大佬一樣
? 一個有夢有戲的人 @怒放吧德德
?分享學習心得,歡迎指正,大家一起學習成長!

在這裡插入圖片描述

前言

好久沒堅持學習了,所以,這次要好好下定決心學習。多執行緒與高併發不是一天兩天就能弄懂的,需要不斷的學習、實踐,本次筆者將最近學習的內容知識記錄下來。多執行緒也是一項比較重要的內容,雖然CRUD不太會接觸到,但是,在一些相關場景可能會有某些問題是由於執行緒導致的。

執行緒的概念

要了解執行緒的概念,就需要知道什麼是程式。簡單理解就是一個程式中包含了許多個執行緒。現在就簡單介紹,後續若是有對作業系統進行研究的話會慢慢介紹,具體關於執行緒程式的內容可以去看看王道的作業系統,裡面講述得特別清楚。

什麼是程式?

是系統進行資源分配的基本單位,是作業系統結構的基礎,程式是執行緒的容器。程式是指令、資料及其組織形式的描述,程式是程式的實體。

什麼是執行緒?

所謂執行緒就是作業系統(OS)能夠進行運算排程的最小單位,是一個基本的CPU執行單元,也是執行程式流的最小單元。能夠提高OS的併發效能,減小程式在併發執行時所付出的時空開銷。執行緒是程式的一個實體,是被系統獨立排程和分派的基本單位。執行緒本身是不擁有系統資源的,但是它能夠使用同屬程式的其他執行緒共享程式所擁有的全部資源。

在Java執行緒中是怎樣的呢?

在Java中,最常見得就是繼承Thread類或者實現Runnable介面,再透過run或者start方法去執行執行緒。
如以下程式碼,這是一段很簡單得程式碼塊,透過繼承Thread類,重寫run方法來建立執行緒,並且透過run和start來執行。

public class Thread_demo01 {
    private static class Thread1 extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("Thread1-" + i);
            }
        }
    }

    public static void main(String[] args) {
        new Thread1().run(); // 順序執行
//        new Thread1().start(); // 執行緒同時執行
        for (int i = 0; i < 5; i++) {
            try {
                Thread.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("main");
        }
    }
}

這兩種不同得啟動方式,出現得現象也是不同的。

  • 使用run方法啟動執行緒
    在這裡插入圖片描述
  • 使用start方法啟動執行緒
    在這裡插入圖片描述
    關於這兩種方式啟動執行緒可以看一下下面的流程圖。
    在這裡插入圖片描述
    run方法是會讓執行緒T1先執行完畢之後,再繼續執行主執行緒,而start方法他是同時執行兩個執行緒。

Java執行緒的Sleep、Yield、Join方法

1、sleep方法

sleep()需要提供一個時間引數(毫秒),會使得執行緒在一定的時間內被暫停執行,在sleep的過程中,執行緒是不會釋放鎖的,只會進入阻塞狀態,讓出cpu給其他執行緒去執行。如下程式碼演示,此處不做鎖的探究。

public class T3_Thread_Sleep {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Thread1());
        Thread t2 = new Thread(new Thread2());
        t1.start();
        t2.start();
    }
    static class Thread1 implements Runnable {
        @Override
        public void run() {
            System.out.println("T1 is running");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("T1 is end");
        }
    }

    static class Thread2 implements Runnable {
        @Override
        public void run() {
            System.out.println("T2 is running");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("T2 is end");
        }
    }
}

執行之後在T1執行緒休眠的時候會讓出cpu資源給T2執行緒,T1執行緒會睡眠1秒,T2睡眠2秒,最終結果如圖
在這裡插入圖片描述

2、Yield方法

yield()與sleep()都是讓執行緒暫停執行,也是不會釋放鎖資源。但是yield並不是讓程式進入阻塞態,而是回到就緒態,等待重新獲取CPU資源。此時,其他的執行緒有機會獲得cpu資源,也有可能在yield方法進入就緒態後立馬變成執行態。如以下程式碼,同樣不考慮鎖的問題。

public class T4_Thread_Yield {
    static class Thread1 implements Runnable {
        @Override
        public void run() {
            System.out.println("T1 is running");
            Thread.yield();
            System.out.println("T1 is end");
        }
    }
    static class Thread2 implements Runnable {
        @Override
        public void run() {
            System.out.println("T2 is running");
            System.out.println("T2 is end");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new Thread1());
        Thread t2 = new Thread(new Thread2());
        t1.start();
        t2.start();
    }
}

經過不同的測試,結果都是不同的。
在這裡插入圖片描述
在這裡插入圖片描述

3、Join方法

join()方法是暫停當前執行緒,呼叫執行另一個執行緒,等待join的執行緒執行完畢後才能夠繼續執行當前執行緒。如以下例子,T1,T2同時開始,在T2執行緒中join了T1,就會導致T1要先執行完畢之後,才會去執行T2。

public class T5_Thread_Join {
    public static void main(String[] args) {
        Thread T1 = new Thread(() -> {
            System.out.println("T1開始");
            for (int i = 0; i < 5; i++) {
                System.out.println("執行緒T1執行中: " + i);
            }
            System.out.println("T1結束");
        });
        Thread T2 = new Thread(() -> {
            System.out.println("T2開始");
            for (int i = 0; i < 5; i++) {
                System.out.println("執行緒T2執行中: " + i);
                if (i == 3) {
                    try {
                        T1.join();
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
             }
            System.out.println("T2結束");
        });
        T1.start();
        T2.start();
    }
}

結果如下,不管怎麼測試,都會是T2最後執行結束。
在這裡插入圖片描述

Java的執行緒狀態

執行緒具有最基本的三態(就緒、執行、阻塞)。執行緒與程式一樣,各執行緒之間也存著共享資源和互相合作的制約關係,致使執行緒執行時具有間斷性。接下來看一下如圖,這是五種狀態的轉化。
在這裡插入圖片描述

執行緒的不同狀態

在Java執行緒中有6中狀態,從執行緒的建立到執行緒的終止。執行緒建立為NEW建立態,透過start啟動執行緒,執行緒內部會從就緒態轉成執行態,在Java執行緒中統稱為“執行態”,執行緒由於被掛起、呼叫yeild等方法能夠使執行緒從執行態轉成就緒態,也能夠透過執行緒的其他方法或者鎖阻塞執行緒,直到時間結束或者是獲得鎖等,從而回到RUNABLE狀態。

  1. 初始(NEW):新建立了一個執行緒物件,但還沒有呼叫start()方法。
  2. 執行(RUNNABLE):Java執行緒中將就緒(Ready)和執行中(Running)兩種狀態籠統的稱為“執行”。執行緒物件建立後,其他執行緒(比如main執行緒)呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,等待被執行緒排程選中,獲取CPU的使用權,此時處於就緒狀態(Ready)。就緒狀態的執行緒在獲得CPU時間片後變為執行中狀態(Running)。
  3. 阻塞(BLOCKED):表示執行緒阻塞於鎖。
  4. 等待(WAITING):進入該狀態的執行緒需要等待其他執行緒做出一些特定動作(通知或中斷)。
  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。
  6. 終止(TERMINATED):表示該執行緒已經執行完畢。

Java執行緒的狀態轉化

如執行緒狀態轉換圖,以下就是Java執行緒狀態的轉換流程。執行緒可以透過實現Runnable介面或者繼承Threa類,然後去例項化Java的執行緒物件。線上程被執行之前都是屬於建立態(NEW),在呼叫start方法後,執行緒就會轉成RUNABLE狀態,在RUNABLE中,當執行緒處於就緒態(Ready)的時候,經過排程分配了cpu資源,這時轉成了執行態,當執行緒被掛起、執行緒執行了yield,執行緒將會退回就緒態。在執行態(RUNABLE),也會透過一些處理而被阻塞或者等待。終止狀態(TERMINATED)是執行緒執行完畢退出,此時,終止態的執行緒不會直接轉成建立態。
在這裡插入圖片描述

Java執行緒狀態程式碼

Java中執行緒的狀態都是在java.lang.Thread.State的列舉類中。
可以看一下以下列舉程式碼,分別為(NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED)六種。

public enum State {
    NEW,

    RUNNABLE,

    BLOCKED,

    WAITING,

    TIMED_WAITING,

    TERMINATED;
}

接下來看一下演示程式碼

public class T6_Thread_State {
    static class Thread1 extends Thread {
        @Override
        public void run() {
            System.out.println("run - 當前執行緒的狀態: " + this.getState());
            for (int i = 0; i < 5; i++) {
                try {
                    System.out.println("sleep前 - 當前執行緒的狀態: " + this.getState());
                    Thread.sleep(1000);
                    System.out.println("sleep後 - 當前執行緒的狀態: " + this.getState());
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 t1 = new Thread1();
        System.out.println("main - 當前執行緒的狀態: " + t1.getState());
        t1.start();
        System.out.println("join前 - 當前執行緒的狀態: " + t1.getState());
        t1.join();
        System.out.println("join後 - 當前執行緒的狀態: " + t1.getState());
    }
}

執行結果,可以只管看到執行緒在執行過程種的狀態切換。
在這裡插入圖片描述

博文推薦

這裡推薦給各位一篇很不錯的部落格文章,是針對Java執行緒的狀態轉換的詳細介紹

?創作不易,如有錯誤請指正,感謝觀看!記得點贊哦!?

相關文章