Java線程之線程狀態的轉換


一、線程狀態

線程的狀態轉換是線程控制的基礎。線程狀態總的可以分爲五大狀態:分別是生、可運行、運行、等待|阻塞|睡眠、死。 簡單描述如下:



1、新狀態:線程對象已經創建,還沒有在其上調用start()方法。

2、可運行狀態:當線程有資格運行,但調度程序還沒有把它選定爲運行線程時線程所處的狀態。當start()方法調用時,線程首先進入可運行狀態,在線程運行之後或者阻塞、等待或睡眠狀態回來後,也返回到可以運行狀態。

3、運行狀態:線程調度陳旭從可運行池中選擇一個線程作爲當前線程時線程所處的狀態。這也是線程進入運行狀態的唯一一種方式。

4、等待|阻塞|睡眠狀態:這是線程有資格運行時它所處的狀態。實際上這是那個狀態可以合爲一種,

其共同點爲:線程仍舊還活着。但是當前沒有條件運行。換句話說,它是可運行的,但是如果某件事情出現,他可能返回到可運行狀態。

5、死亡狀態:當線程的run()方法完成時就可以認爲線程死去了。這個線程對象或許是活的,但是,它已經不是一個單獨可執行的線程。線程一旦死亡,就不能復生。如果在一個死去的線程上調用start()方法,會拋出java.lang.IllegalThreadStateException異常。


線程狀態轉換圖(講究着看看)



二、阻止線程執行

對於線程的阻止,考慮一下三個方面,不考慮IO阻塞的情況:

睡眠、等待

需要一個對象的鎖定而被阻塞。


1、睡眠 sleep()

Thread.sleep(long millis) 和 Thread.sleep(long millis, int nanos)靜態方法強制當前正在執行的線程睡眠(暫停執行),以"減慢線程"。當線程睡眠時,它入睡在某個地方,在甦醒之前不會返回到可運行狀態。當睡覺時間到期,則返回到可運行狀態。

線程睡眠的原因:線程執行太快,或者需要強制進入下一輪,因爲Java規範不保證合理的輪換。

睡眠的實現:調用靜態方法即可。



public void run() {
		int count = 0;
		while (true) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + (count++));
		}
	}

 


睡眠的位置:爲了讓其他線程有機會執行,可以將Thread.sleep()的調用放到線程run()方法之內,這樣才能保證線程執行過程中會睡眠。


例如:



package com.sample.client;

/**
 * 
 * @author DYLAN
 */
public class Client implements Runnable {

	@Override
	public void run() {
		for (int i = 0; i < 5; i++) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}

	public static void main(String[] args) {
		Client client = new Client();
		Thread t = new Thread(client);
		t.setName("阿貓");
		t.start();
		
		Thread t2 = new Thread(client);
		t2.setName("阿狗");
		t2.start();
	}
}
阿貓:0
阿狗:0
阿貓:1
阿狗:1
阿狗:2
阿貓:2
阿貓:3
阿狗:3
阿貓:4
阿狗:4
這樣,線程在每次執行過程中,總會睡眠1000毫秒,只有線程睡眠了,其他的線程就有機會執行了。

注意:
a、線程睡眠是幫組所有線程獲得運行機會的最好方法。
b、線程睡眠到期自動甦醒,並返回到可運行狀態,不是運行狀態。sleep()中指定的時間是線程不會運行的最短時間。因此,sleep()方法不能保證該線程到期後就開始執行。
c、sleep()是靜態方法,只能控制當前正在運行的線程。

2、線程的優先級和線程讓步yield()
線程的讓步是通過Thread.yield()來實現的。yield()方法的作用是:暫停當前正在執行的線程對象,並執行其他線程。
要理解yield()必須瞭解線程優先級的概念,線程總是存在優先級,優先級範圍在1-10之間,JVM線程調度程序是基於優先級的搶先調度機制。在大多數的情況下,當前運行的線程優先級大於或等於線程池中任何線程的優先級。但這僅僅是大多數情況。
注意:當設計多線程應用程序的時候,一定不要依賴於線程的優先級。因爲線程調度優先級操作是沒有保障的,只能把線程優先級作爲一種提高程序效率的方法,但是要保證不依賴這種操作。

當線程池中線程都具有相同的優先級,調度程序的JVM實現自由選擇它喜歡的線程。這時候調度程序的操作有兩種可能:一是選擇一個線程運行,直到它阻塞或者運行完全終止,二是時候切片,爲池內的每個線程提供均等的運行機會。
設置線程的優先級:線程默認的優先級是創建它的執行線程的優先級。可以通過setPriority(int newPriority)更改線程的優先級。
Thread t = new MyThread();
t.setPriority(8);
t.start();
線程優先級爲1-10之間的正整數,JVM從不會改變一個線程的優先級。然而,1-10之間的數值是沒有保證的。一些JVM可能不能識別10個不同的值,而將這些優先級進行每兩個或多個合併,變成至少10個的優先級,則兩個或多個優先級的線程可能被映射爲一個優先級。
線程默認優先級爲5,Thread類中有三個常量,定義線程優先級範圍:
static int MAX_PRIORITY 線程可以具有的最高優先級。
static int MIN_PRIORITY 線程可以具有的最低優先級。
static int NORM_PRIORITY 分配給線程的默認優先級

 3、Thread.yield()方法

Thread.yield()方法作用是:暫停當前正在執行的線程對象,並執行其他線程。yield應該做的是讓當前運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行機會。因此,使用yield的目的是讓相同優先級的線程之間能適當的輪轉執行。但是,實際中無法保證yield達到讓步目的,因爲讓步的線程還有可能被線程調度程序再次選中。

結論:yield()從未導致線程轉到等待|睡眠|阻塞狀態。在大多數情況下,yield()將導致線程從運行狀態轉到可運行狀態,但是可能沒有效果。



4、join()方法

Thread的非靜態方法join()讓一個線程B加入到另外一個線程A的尾部。在A執行完畢之前,B不能執行。

 

 

Client client = new Client();
Thread t = new Thread(client);
t.setName("阿貓");
t.setPriority(8);
t.start();
t.join();
		
Thread t2 = new Thread(client);
t2.setName("阿狗");
t2.start();

 線程t執行完畢後線程t2方能執行。


小結:

到目前爲止,線程離開運行狀態的3中方法:

1、調用Thread.sleep():使當前線程睡眠至少多少毫秒(儘管它可能在指定的時間之前被調度程序中斷)。

2、調用Thread.yield():不能保障太多事情,儘管通常它會讓當前運行線程回到可運行性狀態,使得有相同優先級的線程有機會執行。

3、調用join()方法:保證當前線程停止執行,直到該線程所加入的線程完成爲止,然而,如果它加入的線程沒有存活,則當前線程不需要停止。


除了以上三種方式外,還有下面幾種特殊情況可能使線程離開運行狀態:

1、線程的run()方法正常執行完畢。

2、在對象上調用wait()方法(不是在線程上調用)。

3、線程不能在對象上獲得鎖定,它正試圖運行該對象上的方法代碼。

4、線程調度程序可以決定將當前運行狀態移動到可運行狀態,以便另一個線程獲得運行機會,而不需要任何理由。

 


 

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