前言
上節我們對線程有了個基本的概念和認識,從線程狀態轉變過程我們也已經知道了線程通過調用start方法進行啓動,直到run方法執行線程結束,今天我們就來詳細的說說啓動和終止線程的細節,OK,讓我們開始今天的併發之旅吧。
創建線程
在使用一個線程之前我們需要先構造線程,即new一個線程
Thread thread = new Thread();
線程對象在構建的時候需要提供線程所需要的屬性,如線程組、優先級等等,下面我們看下如下的源代碼:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize) {
// 如果新線程名字爲null,拋出異常
if (name == null) {
throw new NullPointerException("name cannot be null");
}
// 獲取該線程的父線程
Thread parent = currentThread();
// 獲取安全管理組件
SecurityManager security = System.getSecurityManager();
if (g == null) {
// 如果安全組件不爲空,就調用SecurityManager的線程組
if (security != null) {
g = security.getThreadGroup();
}
// 如果SecurityManager爲空,並且該線程的線程組也爲空,則調用其父線程的線程組
if (g == null) {
g = parent.getThreadGroup();
}
}
// 顯示通過 允許線程訪問線程組
g.checkAccess();
// 檢查訪問權限
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
// 對線程組中未使用的線程計數器+1
g.addUnstarted();
// 調用父線程的線程組
this.group = g;
// 調用父線程守護線程
this.daemon = parent.isDaemon();
// 調用父線程的優先級
this.priority = parent.getPriority();
// 將字符串轉換爲新的字符數組
this.name = name.toCharArray();
// 加載父線程的ContextClassLoader
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext = AccessController.getContext();
this.target = target;
setPriority(priority);
// 加載父線程的ThreadLoad
if (parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
// 指定堆棧大小
this.stackSize = stackSize;
// 設置線程ID
tid = nextThreadID();
}
從上面源碼我們可以看到,一個新的線程是由其父線程來進行空間分配的,子線程繼承父線程的優先級,是否爲守護線程、contextClassLoader以及ThreadLocal,最後分配一個唯一的線程ID,新的線程就被創建完畢,在堆內存中等待着被運行。
也可以通過構建Runnable對象來構建線程:
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
...
}
}, "t1");
啓動線程
線程創建完畢,我們就可以開始使用該線程了,啓動一個線程很簡單,直接使用start方法
thread.start();
我們再來對start方法的源碼進行分析:
/**
* 該方法不是給main線程和系統線程調用的
* 由虛擬機創建和設置的組線程
*/
public synchronized void start() {
// 0代表線程的狀態NEW,初始化狀態,如果線程不是初始化狀態,則拋出異常
if (threadStatus != 0)
throw new IllegalThreadStateException();
// 通知線程組,該線程即將被啓動,添加到組的運行線程列表中,同時未使用線程數計數器-1
group.add(this);
// 啓動成功標識符
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
// 如果啓動不成功,線程組做相應啓動失敗處理
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
// 如果什麼都不做,則
}
}
}
執行start方法之後,當前線程(父線程)就會同步通知虛擬機,只要線程規劃器空閒,就應該立即啓動該線程(調用start方法的線程)。
線程中斷
線程中斷表示一個運行中的線程是否被其他線程進行中斷操作,中斷在線程中是由一個Boolean值來標識的,我們看下如何判斷當前線程中斷狀態和相關操作:
-
判斷線程是否被中斷:isInterrupted方法
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread();
thread1.start();
System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
}
結果:線程:Thread-0 是否被中斷:false
-
中斷線程方法:interrupt方法
thread1.interrupt();
System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
結果:線程:Thread-0是否被中斷:true
-
清除中斷標識:currentThread().interrupt()方法
public static void main(String[] args) throws Exception{
Thread thread1 = new Thread();
thread1.start();
thread1.interrupt();
System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
thread1.currentThread().interrupt();
System.out.println("線程:"+thread1.getName()+"是否被中斷:"+thread1.isInterrupted());
}
結果:
線程:Thread-0是否被中斷:true
線程:Thread-0是否被中斷:false
安全的終止線程
上面提到的中斷方式是一種比較常見的終止方式,除此之外還有2種方式,一個是使用一個標誌位來通知線程終止,還有一個就是使用stop方法(不推薦,下面會詳解),下面我們先看看使用標誌位和中斷如何終止線程:
-
使用標誌位Boolean值
public class ThreadStartDemo {
// 線程終止標識位
static volatile Boolean flag = false;
static int time1 = 0;
static int time2 = 0;
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程1啓動");
while (!flag) {
}
time2 = Integer.parseInt(DateUtil.getNowTimestamp());
int i = time2 - time1;
System.out.println("線程t1退出,等待時間爲" + i + "秒");
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
time1 = Integer.parseInt(DateUtil.getNowTimestamp());
System.out.println("線程2啓動,5秒後修改flag的值");
}
}, "t2");
t1.start();
t2.start();
t2.sleep(5000);
flag = true;
}
}
運行結果如下:
5秒過後:注意看,線程以及終止運行
-
使用中斷機制終止線程
public class ThreadExitDemo extends Thread {
public void run() {
System.out.println("線程運行中!!!");
System.out.println("請輸入任意鍵盤值來發出中斷信號");
}
public static void main(String[] args) throws Exception {
Thread thread = new ThreadExitDemo();
thread.start();
System.in.read();
thread.interrupt();
thread.join();
System.out.println("線程已經退出!!!");
}
}
運行結果如下:
線程中幾個廢棄的方法
在線程運行過程中還有三個被拋棄的方法,分別是suspend()、resume()、stop()方法,分別代表暫停、恢復和停止的意思,那麼爲什麼這3個方法被廢棄了呢?
-
suspend方法在被調用後,線程不會釋放已經佔有的資源(如鎖),而是佔着資源進入睡眠狀態,這樣容易引起死鎖問題;
-
resume和suspend是成對出現的,既然suspend被拋棄了,當然好基友resume也就沒有用武之地了,也是不被推薦使用的方法;
-
stop方法在終止一個線程時,不會保證線程的資源正常釋放,通常是沒有給予線程完成資源釋放工作的機會,這就會導致程序可能工作在不確定的狀態下,導致一個線程任務不能完整的執行完就被直接終止,程序執行的正確性得不到保證;
以上就是今天所講的線程啓動和終止的相關內容,希望通過這篇文章,你能對如何正確的啓動和關閉一個線程有所掌握,感謝您的閱讀!!!