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異常
守護線程使用場景
守護線程的典型代表是垃圾回收,這是很多人說守護進程非常有用的理由,但實際上守護進程在用戶開發上的應用場景幾乎用處不大,可能的應用場景:
- 內存資源或者線程的管理,但是非守護線程也可以做
- 守護線程負責一個可以將當前的JVM退出的功能,即將非damon的線程都退出,然後jvm自動退出,感覺用的也非常少,可以直接通知相關線程退出不就可以了,考慮設計上優雅一些,可能有點好處。