一、執行緒棧模型與執行緒的變數
要理解執行緒排程的原理,以及執行緒執行過程,必須理解執行緒棧模型。
執行緒棧是指某一時刻記憶體中執行緒排程的棧資訊,當前呼叫的方法總是位於棧頂。執行緒棧的內容是隨著程式的執行動態變化的,因此研究執行緒棧必須選擇一個執行的時刻(實際上指程式碼執行到什麼地方)。
下面透過一個示例性的程式碼說明執行緒(呼叫)棧的變化過程。
幅圖描述在程式碼執行到兩個不同時刻1、2時候,JVM虛擬機器執行緒呼叫棧示意圖。
當程式執行到t.start();時候,程式多出一個分支(增加了一個呼叫棧B),這樣,棧A、棧B並行執行。
從這裡就可以看出方法呼叫和執行緒啟動的區別了。
二、執行緒狀態的轉換
a、執行緒的狀態
執行緒的狀態轉換是執行緒控制的基礎。執行緒狀態總的可分為五大狀態:分別是生、死、可執行、執行、等待/阻塞。用下圖來描述如下:
1、新狀態:執行緒物件已經建立,還沒有在其上呼叫start()方法。
2、可執行狀態:當執行緒有資格執行,但排程程式還沒有把它選定為執行執行緒時執行緒所處的狀態。當start()方法呼叫時,執行緒首先進入可執行狀態。線上程執行之後或者從阻塞、等待或睡眠狀態回來後,也返回到可執行狀態。
3、執行狀態:執行緒排程程式從可執行池中選擇一個執行緒作為當前執行緒時執行緒所處的狀態。這也是執行緒進入執行狀態的唯一一種方式。
4、等待/阻塞/睡眠狀態:這是執行緒有資格執行時它所處的狀態。實際上這個三狀態組合為一種,其共同點是:執行緒仍舊是活的,但是當前沒有條件執行。換句話說,它是可執行的,但是如果某件事件出現,他可能返回到可執行狀態。
5、死亡態:當執行緒的run()方法完成時就認為它死去。這個執行緒物件也許是活的,但是,它已經不是一個單獨執行的執行緒。執行緒一旦死亡,就不能復生。 如果在一個死去的執行緒上呼叫start()方法,會丟擲java.lang.IllegalThreadStateException異常。
b、阻止執行緒執行
對於執行緒的阻止,考慮一下三個方面,不考慮IO阻塞的情況:
睡眠,等待,阻塞(因為需要一個物件的鎖定而被阻塞)。
1、睡眠
Thread.sleep(long millis)和Thread.sleep(long millis, int nanos)是靜態方法,他們強制將當前正在執行的執行緒休眠(暫停執行),以“減慢執行緒”。當執行緒睡眠時,它入睡在某個地方,在甦醒之前不會返回到可執行狀態。當睡眠時間到期,則返回到可執行狀態。
執行緒睡眠的原因:執行緒執行太快,或者需要強制進入下一輪,因為Java規範不保證合理的輪換。
睡眠的實現:呼叫靜態方法。
try { Thread.sleep(123); } catch (InterruptedException e) { e.printStackTrace(); }
睡眠的位置:為了讓其他執行緒有機會執行,可以將Thread.sleep()的呼叫放執行緒run()之內。這樣才能保證該執行緒執行過程中會睡眠。
例如,在前面的例子中,將一個耗時的操作改為睡眠,以減慢執行緒的執行。可以這麼寫:
public void run() { for (int i = 0; i < 5; i++) { try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(name + ": " + i); } }
執行結果:
yunhe: 0 tianti: 0 yunhe: 1 tianti: 1 yunhe: 2 tianti: 2 yunhe: 3 tianti: 3 yunhe: 4 tianti: 4
這樣,執行緒在每次執行過程中,總會睡眠10毫秒,睡眠後,其他的執行緒就有機會執行了。
注意:
- 執行緒睡眠是幫助所有執行緒獲得執行機會的最好方法。
- 執行緒睡眠到期自動甦醒,並返回到可執行狀態,不是執行狀態。sleep()中指定的時間是執行緒不會執行的最短時間。因此,sleep()方法不能保證該執行緒睡眠到期後就開始執行。
- 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 執行緒可以具有的最高優先順序。 10
static int MIN_PRIORITY 執行緒可以具有的最低優先順序。 1
static int NORM_PRIORITY 分配給執行緒的預設優先順序。 5
3、Thread.yield()方法
Thread.yield()方法作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。
yield()應該做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。
結論:yield()從未導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。
4、join()方法
Thread的非靜態方法join()讓一個執行緒B“加入”到另外一個執行緒A的尾部。在B執行完畢之前,A不能工作。例如:
Thread t = new MyThread(); t.start(); t.join();
另外,join()方法還有帶超時限制的過載版本。 例如t.join(5000);則讓執行緒等待5000毫秒,如果超過這個時間,則停止等待,變為可執行狀態。
執行緒的加入join()對執行緒棧導致的結果是執行緒棧發生了變化,當然這些變化都是瞬時的。下面給示意圖:
小結:
到目前位置,介紹了執行緒離開執行狀態的3種方法:
- 呼叫Thread.sleep():使當前執行緒睡眠至少多少毫秒(儘管它可能在指定的時間之前被中斷)。
- 呼叫Thread.yield():不能保障太多事情,儘管通常它會讓當前執行執行緒回到可執行性狀態,使得有相同優先順序的執行緒有機會執行。
- 呼叫join()方法:保證當前執行緒停止執行,直到該執行緒所加入的執行緒完成為止。然而,如果它加入的執行緒沒有存活,則當前執行緒不需要停止。
除了以上三種方式外,還有下面幾種特殊情況可能使執行緒離開執行狀態:
1、執行緒的run()方法完成。
2、在物件上呼叫wait()方法(不是線上程上呼叫)。
3、執行緒不能在物件上獲得鎖定,它正試圖執行該物件的方法程式碼。
4、執行緒排程程式可以決定將當前執行狀態移動到可執行狀態,以便讓另一個執行緒獲得執行機會,而不需要任何理由。
三、執行緒的同步和鎖
a、同步問題提出
b 、同步和鎖定
1、鎖的原理
public int fix(int y) { synchronized (this) { x = x - y; } return x; }
public synchronized int getX() { return x++; }
public int getX() { synchronized (this) { return x; } }
c、靜態方法同步
public static synchronized int setName(String name){ Xxx.name = name; }
public static int setName(String name){ synchronized(Xxx.class){ Xxx.name = name; } }
d、如果執行緒不能不能獲得鎖會怎麼樣
e、何時需要同步
f、執行緒安全類
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public void add(String name) { nameList.add(name); } public String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; } } }
測試程式碼:
public class Test { public static void main(String[] args) { final NameList nl = new NameList(); nl.add("aaa"); class NameDropper extends Thread{ public void run(){ String name = nl.removeFirst(); System.out.println(name); } } Thread t1 = new NameDropper(); Thread t2 = new NameDropper(); t1.start(); t2.start(); } }
是同步的,但是程式還不是執行緒安全的。
public class NameList { private List nameList = Collections.synchronizedList(new LinkedList()); public synchronized void add(String name) { nameList.add(name); } public synchronized String removeFirst() { if (nameList.size() > 0) { return (String) nameList.remove(0); } else { return null; } } }
這樣,當一個執行緒訪問其中一個同步方法時,其他執行緒只有等待。
public class DeadlockRisk { private static class Resource { public int value; } private Resource resourceA = new Resource(); private Resource resourceB = new Resource(); public int read() { synchronized (resourceA) { synchronized (resourceB) { return resourceB.value + resourceA.value; } } } public void write(int a, int b) { synchronized (resourceB) { synchronized (resourceA) { resourceA.value = a; resourceB.value = b; } } } }
h、執行緒同步小結