Java基礎--多線程詳解

1. 進程 線程 協程

1.1 操作系統類型

作業是用戶在一次處理中要求計算機系統做的操作的集合。
目前操作系統基本上可以分爲3類:

  1. 批處理操作系統
  • 用戶提交作業給系統
  • 系統將若干作業分爲一個批次加載入外存
  • 系統按一定的策略選擇一個作業加載入內存執行
  • 作業運行完成或錯誤
  • 系統輸出已執行的作業,並繼續下一個作業

策略常見的有:先到先服務,優先級等。
優點:
減少人工干預,提交機器利用率。
缺點:
作業阻塞時,系統阻塞;無交互。

  1. 分時操作系統
  • 用戶發出系統使用請求
  • 系統將用戶請求放入時間分配範圍內
  • 用戶使用完成或分配用戶的時間已到,暫停用戶使用,分配下一用戶
  1. 實時操作系統
    實時操作系統和分時操作系統相當類似,甚至其工作基本原理相似。區別在於,分時操作系統中,用戶的請求可能需要很長時間才能執行完成(如果用戶過多的話);在實時操作系統中,不管用戶數量是多少,操作系統必須在指定的時間內進行響應。
    如果分時操作系統的時間片分配的足夠小,那麼,其可以當做一個實時操作系統。

在現代操作系統中絕不是單獨一個類型的操作系統,都是多種模式配合使用。

1.2 線程

線程是操作系統調度的最小單位。線程是一系列作業的組合。
線程是獨立調度和分派的基本單位。線程可以爲操作系統內核調度的內核線程。
線程的實體包含程序、數據和TCB。
線程是動態概念,線程的動態特性有線程控制塊TCB(Thread Control Block)描述。

一個TCB包含以下信息:

  • 線程狀態
  • 線程中斷現場
  • 執行堆棧
  • 線程局部變量主存
  • 進程的主存和其他資源

線程還有一些其他的信息也需要被保存:
程序計數器,狀態參數和返回地址等。
在這裏插入圖片描述

1.3 進程

進程是程序在操作系統中的一次運行活動。
進程是資源分配的基本單位。
進程是操作系統結構的基礎。
進程是線程的容器。
程序由指令,數據和組織形式構成。
進程則是程序在運行環境下的一次運行活動。

狹義:進程是正在運行的程序的實例
廣義:進程是一個具有一定獨立的程序的關於數據集合的一次運行活動。
獨立程序,表示程序其有靜態的代碼實現(二進制也是代碼的一種體現)
數據集合,運行時環境。

在這裏插入圖片描述
進程的動態特性是PCB實現的。

1.4 線程和進程的關係

前面有提到,進程可以看做是線程的容器。不管是進程還是線程都有自己的數據區,代碼區以及動態控制器。
線程可以訪問線程所屬的進程的資源,包含IO網絡等資源。
線程或者進程都有自己的狀態,數據以及堆棧。
線程依賴於進程。
進程間通信IPC,線程之間通信可以直接讀寫數據(需進行併發處理)
現代操作系統中,進程是資源調度的最小單位;線程是執行調度的最小單位。

因此,線程的上下文信息(狀態,中斷恢復,局部變量數據,程序調用堆棧等)遠遠小於進程的上下文信息。
所以,線程的上下文切換快於進程的上下文切換。

1.5 協程

相比於進程,線程的上下文信息已經很小了,但是往往有些切換,有效操作的時間小於線程上下文切換的時間。也就是說,線程的上下文切換,還是浪費資源。
在此基礎上,提出了協程的概念,也叫微線程。
協程簡化了線程的上下文組成,只保留了程序調用堆棧和局部變量數據以及返回地址等。
協程非常類似子程序調用過程。
目前協程直接支持的編程語言不多。

1.6 協程和線程的關係

協程是基於線程的,所以協程切換不用切換線程上下文。也就是說,協程的調度中需要的資源是及其小的,幾乎可以忽略不計。
協程是線程自己調度的,可以根據業務進行調度。
協程不需要線程併發的鎖機制,不存在併發寫讀等衝突。

1.7 並行&串行

並行與串行:

串行:
嘴巴和鼻子,在同一時刻,要麼呼吸,要麼喝水進食。
你永遠無法做到,同時呼吸,同時喝水進食。會被嗆到。
這就是串行,某一個時刻只能做一件事情。
因爲呼吸與喝水進食,並用咽喉。不管是進食,還是呼吸,都需要經過咽喉。一個人只有一個咽喉,所以,同一時間只能做一件事情。

並行:
每個人都可以一邊進食,一邊聽歌。
因爲進食與聽歌使用的是不同的器官,他們之間沒有衝突。所以可以在同一時刻,做多件事情。

在計算機的世界中,計算機進行操作的單位是處理器核心。如果處理器只有一個核心,那麼就是串行的,如果處理器有多個核心,那麼可以串行可以並行。

請注意:並行和串行研究的是時刻。

1.8 併發&並行

在過去的一個小時內,我吃了蔬菜,吃了米飯,喝了水。
可以說,在這一個小時內,我併發吃了蔬菜,米飯以及喝水。
所以,併發研究的是時間段,並行研究的是時間點。

1.9 阻塞&非阻塞

阻塞是指當線程或進程在運行狀態,因請求資源,長時間未得到資源,被操作系統掛起,改變了狀態,切換了上下文。
非阻塞是指當線程或進程在運行狀態,請求資源,在資源未得到響應期間,不會被操作系統掛起,不會修改狀態,不會進行上下文切換。

1.10 同步&異步

同步:
進程請求資源,在資源響應期間,進程沒有做任何事情,在等待資源響應。

異步:
進程請求資源,在資源響應期間,進程還做了其他的事情;資源響應後,繼續執行請求資源後面的操作。

一般來說,同步是阻塞的,異步是非阻塞的。
異步一般性能優於同步。

2. 線程創建

2.1 線程組成

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
線程狀態

2.2 任務

線程可以驅動任務,任務就是線程需要執行的操作。
任務可以由Runnable描述。

    static class LiftOff implements Runnable{

        protected int countDown = 10;
        private static int taskCount = 0;
        private final int id = taskCount++;
        public LiftOff(){}
        public LiftOff(int countDown){
            this.countDown = countDown;
        }

        public String status(){
            return "#" + id + "(" +
                    (countDown > 0 ? countDown : "LiftOff!") + ").";
        }

        @Override
        public void run() {
            while (countDown-- >=0 ){
                System.out.println(status());
                Thread.yield();
            }
        }
    }

這個任務是:打印當前的線程信息,以及線程內的計數器。
每打印一次,就讓出CPU執行權,等待下一次調度執行。

public static void main(String[] args) throws InterruptedException {
        LiftOff liftOff = new LiftOff();
        liftOff.run();
}

可以發現,自始至終,都是一個線程在運行。
在這裏插入圖片描述
藉助於調試,可以很清楚的明白,方法調用都是在main線程中。

2.3 線程驅動任務

我們在2.2中創建了任務,但是這個任務如果由主線程調用,那麼就不是多線程了。而是方法調用。
所以任務還需要進行線程驅動,如何進行線程驅動呢?

public static void main(String[] args) throws InterruptedException {
        LiftOff liftOff = new LiftOff();
        new Thread(liftOff).start();
    }

在這裏插入圖片描述
這個時候,執行方法已經不是由main線程進行執行了,此時main線程已經結束了。
如果到這裏還不明顯,那麼,我們在主線程裏面循環創建多個線程:

public static void main(String[] args) throws InterruptedException {
        LiftOff liftOff = new LiftOff();
        for(int i = 0; i < 6;i++){
            new Thread(liftOff).start();
        }
    }

在這裏插入圖片描述
如果是串行執行呢?
在這裏插入圖片描述
可以看到,串行是非常整齊的。但是如果是多線程,那麼其執行的調入調出就不是可預測的了。

2.4 線程這樣使用的問題

main線程創建了新的線程後,main線程沒有捕獲新創建的線程的任何引用。在使用普通對象時,這對於垃圾回收來說是一場公平的遊戲,但是在使用Thread時,情況就不同了。每個Thread都註冊了他自己:
在這裏插入圖片描述
因此,確實有一個對它的引用,而且,在它的任務退出run方法並死亡之前,垃圾回收器都無法清除。
線程不僅僅包含任務,還有線程自己的一些線程信息,所以,線程創建與回收都是一件浪費資源的事情。
那麼,對於任務區別不大的任務,能不能用已有的線程驅動,避免線程的創建與回收?

3. 線程池

3.1 Executor&ExecutorService

Executor是一個接口,定義了唯一的方法,參數就是任務。
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
ExecutorService定義了線程常用的統一的接口。

3.2 Executors

在這裏插入圖片描述
在這裏插入圖片描述在這裏插入圖片描述
Executors的構造方法是私有的,也就是說,它不允許被實例化,他只是負責做一個代理工具。

3.1 newCachedThreadPool

    public static void main(String[] args) throws InterruptedException {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0;i < 6;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();
    }

在這裏插入圖片描述
調用Executors的newCachedThreadPool的方法創建了一個線程池,初始0個線程,最多Integer.MAX_VALUE個線程。
一個線程超時存活時間爲60秒。
如果線程的任務比較耗費資源,長時間無法釋放線程,那麼,在短時間創建大量的線程,很有可能出現OOM.
阿里編碼規範中強制不允許使用此種方式創建線程。
調用了shutdown方法後,不在接受新的任務,而Executors線程將在所有子線程完成後儘快結束。

3.2 newFixedThreadPool

        ExecutorService service = Executors.newFixedThreadPool(6);
        for (int i = 0;i < 6;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();

在這裏插入圖片描述
在這裏插入圖片描述
FixedTreadPool會在一開始就初始化指定的線程數量,在後面使用的時候,將會重複使用這些線程。
不過,依然不推薦使用FixedTreadPool。因爲FixedThreadPool允許創建的現場數量最多是Integer.MAX_VALUE,這很可能會引發OOM.

3.3 newScheduledThreadPool

        ExecutorService service = Executors.newScheduledThreadPool(3);
        for (int i = 0;i < 6;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();

在這裏插入圖片描述
在這裏插入圖片描述
這和CachedThreadPool類似,最多可創建Integer.MAX_VALUE個線程,也是不推薦使用的。

3.4 newSingleThreadExecutor

        ExecutorService service = Executors.newSingleThreadExecutor();
        for (int i = 0;i < 6;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();

這是線程池數量爲1的線程池。
在這裏插入圖片描述
這個線程池會初始化1一個線程,同時最多允許創建1個線程。
在這裏插入圖片描述
不過依然存在任務數量過多的問題。

3.5 ThreadPoolExecutor

        ExecutorService service = new ThreadPoolExecutor(3,5,
                0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(5));
        for(int i = 0;i<6;i++){
            service.execute(new LiftOff());
        }
        service.shutdown();

手動創建線程池,就可以根據業務創建需要的線程池。

4. Callable

在2.2中的任務是沒有返回值的,如果任務需要將計算結果返回,那麼使用Runnable就難以實現有返回值的任務。
基於此,Callable可以更好的定義有返回值的任務。

    static class RightOff implements Callable<String> {

        private int id;

        public RightOff(int id) {
            this.id = id;
        }

        @Override
        public String call() throws Exception {
            return "result of " + Thread.currentThread().getId() + ":" + id;
        }
    }
ExecutorService service = new ThreadPoolExecutor(5, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        ArrayList<Future<String>> results = new ArrayList<>();
        for (int i = 0;i < 10;i++){
            results.add(service.submit(new RightOff(i)));
        }
        for(Future<String> future : results){
            try {
                System.out.println(future.isDone());
                System.out.println(future.get());
            } catch (ExecutionException e){
				System.out.println("ExecutionException");
                return;
            } catch (Exception e){
                e.printStackTrace();
                return;
            } finally {
                service.shutdown();
            }
        }

submit方法會產生Future對象,它用Callable返回結果的特定類型進行參數化。
可以調用isDone方法查詢Future是否已經完成。
當任務完成時,具有返回結果,可以調用get方法獲取結果。
也可以不用isDone方法判斷是否完成,而是直接調用get方法獲取結果。但是這種方法是阻塞的,程序main線程會一直阻塞在get方法,等待返回。爲了防止長時間程序阻塞,可以調用帶有超時時間的get方法。
使用帶有超時時間的get方法,如果在指定時間內,子線程沒有交出結果,就會進行快速失敗,拋出TimeOutException異常。

5. 線程狀態(生命週期)

通過查看Thread內部類,我們可以知道,線程總共有5個狀態。
在這裏插入圖片描述
有些資料,將TIME_WAITING和WAITING放在了一起。
在Java中則是分開的。
在這裏插入圖片描述
其中WAITING和TIME_WAITING都是BLOCKED狀態

5.1 NEW

當程序使用 new 關鍵字創建了一個線程之後,該線程就處於新建狀態,此時僅由 JVM 爲其分配
內存,並初始化其成員變量的值。

5.2 RUNNABLE

當線程對象調用了 start()方法之後,該線程處於就緒狀態。Java 虛擬機會爲其創建方法調用棧和
程序計數器,等待調度運行。

5.3 RUNNING

如果處於就緒狀態的線程獲得了 CPU,開始執行 run()方法的線程執行體,則該線程處於運行狀
態。

5.4 BLOCKED

阻塞狀態是指線程因爲某種原因放棄了 cpu 使用權,也即讓出了 cpu timeslice,暫時停止運行。
直到線程進入可運行(runnable)狀態,纔有機會再次獲得 cpu timeslice 轉到運行(running)狀
態。阻塞的情況分三種:

  • 等待阻塞:
    RUNNING狀態執行了wait方法,JVM會把線程放入等待隊列(waiting queue)中。
  • 同步阻塞:
    RUNNING狀態的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池(Lock Pool)中。
  • 其他阻塞:
    RUNNINNGN狀態的線程執行Thread.sleep或者Thread.join方法,或者發出資源請求時,JVM會把該線程設置爲阻塞狀態。當sleep狀態超時,hoin等待線程終止或者超時,以及資源請求被響應時,線程重新進入RUNNABLE狀態。

5.5 DEAD(TERMINATED)

線程死亡都是通過以下三種方式的。線程結束後,就是線程死亡狀態了。

  • 正常結束:
    run或者call方法執行完成,線程正常結束。
  • 異常結束:
    線程拋出一個未捕獲的Exception或者Error.
  • 調用stop:
    直接調用線程的stop方法結束線程(這種方式不安全,容易造成死鎖)

6. 終止線程的4種方式

6.1 正常運行結束

程序正常運行,直到結束。

6.2 使用退出標誌退出線程

嚴格來說,使用退出標誌退出線程,採用的也是正常運行結束的方式退出。

    static class Study implements Runnable{

        private volatile boolean exit = false;
        @Override
        public void run() {
            while (!exit){
                // TODO: doSomething
            }
        }
    }

6.3 Interrupt中斷線程

6.3.1 線程阻塞

當線程本身處於阻塞狀態時,調用線程的interrupt,拋出InterruptException異常。請注意,這個異常的拋出位置是阻塞的方法拋出的。線程捕獲異常後,進行循環控制,實現正常退出線程。

    static class People extends Thread{
        @Override
        public void run() {

            while (true){
                try {
                    System.out.println(Thread.currentThread().getName() + " sleep 1000 !");
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    System.out.println("InterruptException");
                    break;
                }
            }
            System.out.println(Thread.currentThread().getName()+"is down!");

        }
    }
        People people = new People();
        people.start();
        System.out.println("people is started!");
        System.out.println("main thread sleep 5000 !");
        try{
            Thread.sleep(5000);
        }catch (InterruptedException e){
            return;
        }
        people.interrupt();
        System.out.println("people interrupt!");

執行結果:
在這裏插入圖片描述
在這裏插入圖片描述
主要應該是調用了native方法。

6.3.2 線程不阻塞

當線程不處於阻塞狀態的時候,可以通過調用isInterrupt方法判斷線程的中斷標誌退出線程。
當調用interrupt方法時,中斷標誌被置位true.

    static class Goods extends Thread {
        @Override
        public void run() {
            int x = 0;
            System.out.println(getName() + " is in run method!");
            while (!isInterrupted()) {
                System.out.println(getName() + " input while " + x);
                x++;
            }
            System.out.println(getName() + " is out run method!");
        }
    }
        System.out.println(Thread.currentThread().getName() + " is start ");
        Goods goods = new Goods();
        System.out.println(Thread.currentThread().getName() + " goods is new ");
        goods.start();
        System.out.println(Thread.currentThread().getName() + " goods is running");
        System.out.println(Thread.currentThread().getName() + " sleep 3 ");
        Thread.sleep(3);
        goods.interrupt();

執行結果:
在這裏插入圖片描述
在這裏插入圖片描述

6.4 stop方法終止線程

程序中可以直接使用 thread.stop()來強行終止線程,但是 stop 方法是很危險的,就象突然關
閉計算機電源,而不是按正常程序關機一樣,可能會產生不可預料的結果,不安全主要是:
thread.stop()調用之後,創建子線程的線程就會拋出 ThreadDeatherror 的錯誤,並且會釋放子
線程所持有的所有鎖。一般任何進行加鎖的代碼塊,都是爲了保護數據的一致性,如果在調用
thread.stop()後導致了該線程所持有的所有鎖的突然釋放(不可控制),那麼被保護數據就有可能呈
現不一致性,其他線程在使用這些被破壞的數據時,有可能導致一些很奇怪的應用程序錯誤。因
此,並不推薦使用 stop 方法來終止線程。

7. sleep,wait,yield,join

  • sleep
    sleep()方法需要指定等待的時間,它可以讓當前正在執行的線程在指定的時間內暫停執行,進入阻塞狀態,該方法既可以讓其他同優先級或者高優先級的線程得到執行的機會,也可以讓低優先級的線程得到執行機會。但是sleep()方法不會釋放“鎖標誌”,也就是說如果有synchronized同步塊,其他線程仍然不能訪問共享數據。
  • wait
    wait()方法需要和notify()及notifyAll()兩個方法一起介紹,這三個方法用於協調多個線程對共享數據的存取,所以必須在synchronized語句塊內使用,也就是說,調用wait(),notify()和notifyAll()的任務在調用這些方法前必須擁有對象的鎖。注意,它們都是Object類的方法,而不是Thread類的方法。
      wait()方法與sleep()方法的不同之處在於,wait()方法會釋放對象的“鎖標誌”。當調用某一對象的wait()方法後,會使當前線程暫停執行,並將當前線程放入對象等待池中,直到調用了notify()方法後,將從對象等待池中移出任意一個線程並放入鎖標誌等待池中,只有鎖標誌等待池中的線程可以獲取鎖標誌,它們隨時準備爭奪鎖的擁有權。當調用了某個對象的notifyAll()方法,會將對象等待池中的所有線程都移動到該對象的鎖標誌等待池。
      除了使用notify()和notifyAll()方法,還可以使用帶毫秒參數的wait(long timeout)方法,效果是在延遲timeout毫秒後,被暫停的線程將被恢復到鎖標誌等待池。
  • yield
    yield()方法和sleep()方法類似,也不會釋放“鎖標誌”,區別在於,它沒有參數,即yield()方法只是使當前線程重新回到可執行狀態,所以執行yield()的線程有可能在進入到可執行狀態後馬上又被執行,另外yield()方法只能使同優先級或者高優先級的線程得到執行機會,這也和sleep()方法不同。
  • join
    join()方法會使當前線程等待調用join()方法的線程結束後才能繼續執行。

https://blog.csdn.net/xiangwanpeng/article/details/54972952

8. start與run

  • start
    用start方法來啓動線程,真正實現了多線程運行,這時無需等待run方法體代碼執行完畢而直接繼續執行下面的代碼。通過調用Thread類的start()方法來啓動一個線程,這時此線程處於就緒(可運行)狀態,並沒有運行,一旦得到cpu時間片,就開始執行run()方法,這裏方法 run()稱爲線程體,它包含了要執行的這個線程的內容,Run方法運行結束,此線程隨即終止。
  • run
    run()方法只是類的一個普通方法而已,如果直接調用Run方法,程序中依然只有主線程這一個線程,其程序執行路徑還是隻有一條,還是要順序執行,還是要等待run方法體執行完畢後纔可繼續執行下面的代碼,這樣就沒有達到寫線程的目的。

總結:調用start方法方可啓動線程,而run方法只是thread的一個普通方法調用,還是在主線程裏執行。這兩個方法應該都比較熟悉,把需要並行處理的代碼放在run()方法中,start()方法啓動線程將自動調用 run()方法,這是由jvm的內存機制規定的。並且run()方法必須是public訪問權限,返回值類型爲void。

9. daemon

9.1 什麼是daemon進程

指在程序運行的時候在後臺提供一種通用服務的線程,並且這種線程並不屬於程序中不可或缺的部分。
當程序中全部是後臺進程時,後臺進程將自動終止,同時系統也結束了。

9.2 優先級

daemon進程的優先級比較低,用於爲系統中的其他對象和線程提供服務。

        Runnable daemonDemo = () -> {
            try{
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e){
                System.out.println("daemon demo interrupt exception");
            } finally {
                System.out.println("deamon demo finally");
            }
        };
        Thread daemon = new Thread(daemonDemo);
        daemon.setDaemon(true);
        daemon.setPriority(Thread.MAX_PRIORITY);
        daemon.start();
        System.out.println("daemon priority is " + daemon.getPriority());
        System.out.println("main priority is " + Thread.currentThread().getPriority());

在這裏插入圖片描述
JDK中有10個優先級,但是JDK與多數操作系統都不能映射很好。
Windows有7個優先級,且不是固定的,所以映射關係也不是確定的。
sun的solaris有2^31個優先級。

所以,調整優先級的時候,儘可能使用MAX_PRIORITY,NORM_PRIORITY,MIN_PRIORITY三種級別。

9.3 daemon創建

一個線程在創建之後,啓動之前,調用setDaemon方法設置是否是daemon線程
在這裏插入圖片描述
如果在啓動之後調用,則會拋出異常。

9.4 daemon傳遞性

daemon線程創建的線程也是daemon線程。

        Runnable daemonDemo = () ->{
            System.out.println("daemon by main start");
            Runnable daemon = () -> {
                System.out.println("daemon by daemon start");
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e){
                    System.out.println("daemon by daemon interrupt exception");
                } finally {
                    System.out.println("daemon by daemon finally");
                }
            };
            System.out.println("daemon by main create thread");
            Thread daemonByDaemon = new Thread(daemon);
            System.out.println("daemon by daemon get isDaemon : " + daemonByDaemon.isDaemon());
            daemonByDaemon.start();
        };
        Thread daemonDemoThread = new Thread(daemonDemo);
        daemonDemoThread.setDaemon(true);
        System.out.println("daemon by main get isDaemon : " + daemonDemoThread.isDaemon());
        daemonDemoThread.start();
        TimeUnit.SECONDS.sleep(2);

在這裏插入圖片描述

9.5 daemon生命週期

daemon運行在後臺的一種特殊進程。它獨立於控制終端並且週期性地執行某種任務或等待處理某些發生的事件。也就是說守護線程不依賴於終端,但是依賴於系統,與系統“同生共死”。當 JVM 中所有的線程都是守護線程的時候,JVM 就可以退出了;如果還有一個或以上的非守護線程則 JVM 不會退出。
基於此性質,可能存在這樣的情況:finally塊可能不會被執行

        Runnable daemonDemo = () -> {
            try{
                System.out.println("daemon demo is starting run !");
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e){
                System.out.println("interrupt exception !");
            } finally {
                System.out.println("finally daemon demo !");
            }
        };
        System.out.println("c create daemon thread");
        Thread daemonThread = new Thread(daemonDemo);
        System.out.println("main thread set daemon is true");
        daemonThread.setDaemon(true);
        System.out.println("main thread start");
        daemonThread.start();
        System.out.println("main is over");
        TimeUnit.MILLISECONDS.sleep(300);

守護線程,sleep1秒後,應該執行finally語句。但是因爲主線程只sleep300毫秒,300毫秒後,主線程結束,此時守護線程也就結束了。
在這裏插入圖片描述

10. 自管理Runnable

實現線程可以繼承Thread類。但是有時候,我們還需要繼承其他的類的時候,就只能實現Runnable接口,實現了接口,則需要調用者去驅動任務。
當有時候,我們需要將驅動工作也封裝,就需要使用自管理的Runnable。
自管理的Runnable也很簡單,舉例說明:
假設有類A,封裝了一些通用操作。類B需要繼承類A,然後實現Runablle接口。
爲了實現自管理,需要在類B中增加屬性Thread,通過傳入類B自身,進行創建自身的Thread,然後提供獲取本身Thread的方法,進行處理。

    static class Father {
        public void say() {
            System.out.println("father say!");
        }
    }

    static class Child extends Father implements Runnable {

        private Thread thread = new Thread(this);

        {
            thread.start();
        }

        public Thread getThread(){
            return thread;
        }

        @Override
        public void run() {
            try {
                while(true){
                    System.out.println("child run!");
                    TimeUnit.SECONDS.sleep(1);
                }
            } catch (InterruptedException e) {
                System.out.println("child interrupt exception!");
            } finally {
                System.out.println("child finally!");
            }
        }
    }

        Child child = new Child();
        child.say();
        TimeUnit.SECONDS.sleep(3);
        System.out.println(child.getThread().isAlive());
        child.getThread().interrupt();

運行結果:
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章