有關線程


title: 有關線程
date: 2019-04-27
tags: java

有關線程

在這裏插入圖片描述

線程的生命週期體可以分爲如下5個主要的階段

  • NEW
  • RUNNABLE
  • RUNNING
  • BLOCKED
  • TERMINATED

線程的NEW狀態
當我們用關鍵字new創建了一個Thread對象時,此時它並不處於執行狀態,因爲沒有調用start方法啓動該線程,那麼線程的狀態爲NEW狀態,準確的說,它只是Thread對象的狀態,因爲在沒有start之前,該現線程根本不存在,與你用關鍵字new創建一個普通的java對象沒什麼區別。
New狀態通過start方法進入RUNNABLE狀態

線程的RUNNABLE狀態
線程對象進入RUNNABLE狀態必須調用start方法,此時纔是真正地在JVM進程中創建了一個線程,線程一經啓動是不會立即就得到執行的,線程的運行與否和進程一樣都要聽命於CPU的調度,所以這個中間狀態稱之爲可執行狀態,也就是RUNNABLE狀態,也就是說它具備執行的資格,但是並沒有真正的執行起來,而是在等待CPU的調度
由於存在Running狀態,所以不會直接進入BLOCKED狀態和TERMINATED狀態,即使是在線程的執行邏輯中調用wait,sleep或者其他block的io操作等,也必須先獲得CPU的調度執行權纔可以,嚴格來講,RUNNABLE的線程只能意外終止或者進入RUNNING狀態

線程的BLOCKED狀態
線程在BLOCKED狀態中可以切換爲如下幾個狀態:
 直接進入TERMINATED狀態,比如調用JDK已經不推薦使用的stop方法或者意外死亡(JVM Crash)
 線程阻塞的操作結束,比如讀取了想要的數據字節進入到RUNNABLE狀態
 線程完成了指定時間的休眠,進入到了RUNNABLE狀態
 Wait中的線程被其他線程notify/notifyall喚醒,進入RUNNABLE狀態
 線程獲取到了某個鎖資源,進入RUNNABLE狀態
 線程在阻塞過程中被打斷,比如其他線程調用了interrupt方法,進入RUNNABLE狀態

線程的TERMINATED狀態
TERMINATED是一個線程的最終狀態,在該狀態中線程將不會切換到其他任何狀態,線程會進入TERMINATED狀態,意味着該線程的整個生命週期都結束了,下列這些情況將會使線程進入TERMINATED狀態
 線程運行正常結束,結束生命週期
 線程運行出錯意外結束
 JVM Crash,導致所有的線程都結束

Thread start方法源碼:
在這裏插入圖片描述在這裏插入圖片描述
其中最核心的部分數start0這個本地方法,也就是JNI方法

在這裏插入圖片描述

總結出以下幾點:
 Thread被構造後的NEW狀態,事實上threadStatus這個內部屬性爲0
 不能兩次啓動Thread,否則就會出現IllegalThreadStateException異常
 線程啓動後將會被加入到一個ThreadGroup中
 一個線程生命週期結束,也就是到了TERMINATED狀態,再次調用start方法是不允許的,也就是說TERMINATED狀態是沒有辦法回到RUNNABLE/RUNNING狀態的

模板設計模式在Thread中的應用:

package com.winkilee.Thread;

/**
 * @Author WinkiLee
 * @Date 2019/4/27 14:46
 * @Description 模板設計模式在Thread中的應用
 */
public class TemplateMethod {

    public final void print(String message){
        System.out.println("############");
        warpPrint(message);
        System.out.println("############");
    }

    protected void warpPrint(String message){
    }

    public static void main(String[] args) {
        TemplateMethod t1=new TemplateMethod(){
            @Override
            protected void warpPrint(String message){
                System.out.println("*"+message+"*");
            }
        };
        t1.print("hello thread");

        TemplateMethod t2=new TemplateMethod(){
            @Override
            protected void warpPrint(String message){
                System.out.println("+"+message+"+");
            }
        };
        t2.print("hello thread");
    }
}

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

Print方法類似於Thread的start方法,而warpPrint則類似於run方法,這樣做的好處是,程序結構由父類控制,並且是final修飾的,不允許被重寫,子類只需要實現想要的邏輯任務即可。

Thread模擬營業大廳叫號機程序在這裏插入圖片描述

假設大廳共有四臺出號機,就意味着四個線程在工作,約定當天最多受理50筆業務,也就是號碼最多可以出到50:

package com.winkilee.Thread;

/**
 * @Author WinkiLee
 * @Date 2019/4/27 15:03
 * @Description 多線程模擬營業廳叫號機
 */
public class TicketWindow extends Thread{

    private final String name;

    private static final int MAX=50;

    private static int index=1;

    public TicketWindow(String name) {
        this.name = name;
    }

    @Override
    public void run(){
        while (index<=MAX){
            System.out.println("窗口"+name+"呼叫號碼"+(index++));
        }
    }


    public static void main(String[] args) {
        TicketWindow ticketWindow1=new TicketWindow("一號窗口");
        ticketWindow1.start();

        TicketWindow ticketWindow2=new TicketWindow("二號窗口");
        ticketWindow2.start();

        TicketWindow ticketWindow3=new TicketWindow("三號窗口");
        ticketWindow3.start();

        TicketWindow ticketWindow4=new TicketWindow("四號窗口");
        ticketWindow4.start();
    }
}

運行結果如下:

在這裏插入圖片描述

線程與線程組
Thread與ThreadGroup

在Thread的構造函數中,可以顯示地指定線程的Group,也就是ThreadGroup
Thread init方法源碼
在這裏插入圖片描述
可以看出,如果在構造Thread的時候沒有顯示地指定一個ThreadGroup,那麼子線程將會被加入到父線程所在的線程組
Demo:

package com.winkilee.test;

/**
 * @Author WinkiLee
 * @Date 2019/4/27 15:33
 * @Description 線程組測試用例
 */
public class ThreadConstruction {

    public static void main(String[] args) {
        Thread t1=new Thread("t1");

        ThreadGroup group=new ThreadGroup("TestGroup");

        Thread t2=new Thread(group,"t2");

        ThreadGroup mainThreadGroup=Thread.currentThread().getThreadGroup();

        System.out.println("主線程屬於線程組"+mainThreadGroup.getName());
        System.out.println("t2屬於線程組"+t2.getThreadGroup().getName());
        System.out.println("t1屬於線程組"+t1.getThreadGroup().getName());

    }
}

運行結果:

在這裏插入圖片描述

守護線程demo

package com.winkilee.demo;

/**
 * @Author WinkiLee
 * @Date 2019/4/27 15:57
 * @Description 守護線程demo
 */
public class DaemonThread {

    public static void main(String[] args) throws InterruptedException {
        //main線程開始

        Thread thread = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        //thread.setDaemon(true);

        thread.start();
        Thread.sleep(2_000L);
        System.out.println("main線程執行完畢");

    }
}

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

發現程序並沒有退出執行
即使main線程正常的結束了自己的生命週期,原因就是因爲JVM進程裏還存在一個非守護線程在運行

如果打開註釋//thread.setDaemon(true);
也就是將thread設置爲了守護線程,那麼main線程在結束生命週期後,JVM也會隨之退出,當然thread線程也會結束

設置守護線程setDaemon方法只在線程啓動之前才能生效,如果一個線程已經死亡,那麼設置setDaemon就會拋出IllegalThreadStateException異常

守護線程使用場景
守護線程的典型代表是垃圾回收,這是很多人說守護進程非常有用的理由,但實際上守護進程在用戶開發上的應用場景幾乎用處不大,可能的應用場景:

  1. 內存資源或者線程的管理,但是非守護線程也可以做
  2. 守護線程負責一個可以將當前的JVM退出的功能,即將非damon的線程都退出,然後jvm自動退出,感覺用的也非常少,可以直接通知相關線程退出不就可以了,考慮設計上優雅一些,可能有點好處。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章