3、線程狀態
線程存在多種狀態,包括新狀態、可運行狀態、運行狀態、等待/被阻塞/睡眠/監控/掛起狀態和死狀態5中狀態。
1)、新狀態
新狀態是指線程執行個體化後,但start方法還沒有在該線程上被調用時所處的狀態。它還沒有達到準備啟動並執行狀態,更不是一個執行線程。
2)、可運行狀態
Start()方法啟動後,線程由新狀態進入可運行狀態。可運行狀態就是準備好,可以運行但還沒有啟動並執行狀態。
不但stat()方法可以進入運行狀態,而且從等待/被阻塞/睡眠/監控/掛起狀態也可以進入可運行狀態。如調用sleep()方法結束後,調用yield(0方法結束後所作用的線程都進入可運行狀態。
可運行狀態也可以說是CPU選擇線程的狀態。所有的線程不分主次、前後,只要處於可運行狀態都有可能被CPU選中運行,當然有可能本線程處於可運行狀態,但是很長時間不運行它,這個是一種特殊情況。還有其中情況就是總是運行一個線程,如在一個線程中調用yield()方法後進入可運行狀態,但是下面啟動並執行還是這個線程。
3)、運行狀態
就是當前執行線程所處的狀態,它可以轉入可運行狀態和等待/被阻塞/失眠/監控/掛起狀態,但是只能從可運行狀態進入運行狀態。
4)、等待/被阻塞/睡眠/監控/掛起狀態
等待/被阻塞/睡眠/監控/掛起狀態很複雜,它是很多狀態的集合體,但是都起到從運行狀態到可運行狀態的中介作用。處於運行狀態的線程由於某種原因不能繼續運行,例如調用sleep()方法、被調用join()方法,此線程就會由運行狀態進入等待/被阻塞/睡眠/監控/掛起狀態。而當sleep()方法結束後或調用join()方法的線程結束後,該線程就又進入可運行狀態。
進入等待/被阻塞/睡眠/監控/掛起狀態的方法除了sleep()、yield()方法和join()方法外,還有suspend()方法,它是指讓一個線程把另一個線程掛起。
5)、死狀態
當線程中的run()方法完成之後,線程就進入了死狀態。對處於一個死狀態的線程帶哦用start()方法會發生異常。
4、線程的調度
線程發送器是JVM中的一部分,它決定在任意指定的時刻應該運行哪個線程,並吧線程帶出運行狀態。線程的調度定義了java運行環境如何交換任務以及如何選擇下一個即將被執行的任務。對線程進行調度又通過優先順序、sleep()、yield()、join()方法來完成。在前面說過CPU對線程的選擇是不確定的,所以通過調度知識讓線程按照某種方式運行,而不能徹底規定它。
1)、優先順序
用來判定何時允許某個線程運行。理論上優先順序高的線程比優先順序低的線程可獲得更多的CPU時間。實際上獲得CPU時間長度與很多因素有關,不能僅靠優先順序來判斷。設計線程的優先順序使用setPriority(int level)方法來設定。在level值中,用MIN_PRIORITY來表示優先順序最小1;用MAX_PRIORITY來表示優先順序最大10;線程的預設優先順序為5即NORM_PRIORITY。
獲得當前線程的優先順序的方法是getPriority(),其一般形式為getPriority()。
看下面應用優先順序的程式,它通過執行迴圈、記錄次數來表現出優先順序的高低:
public class test{ public static void main(String[] str) { Thread.currentThread().setPriority(Thread.MAX_PRIORITY); MyThread t1 = newMyThread(Thread.NORM_PRIORITY + 2); MyThread t2 = newMyThread(Thread.NORM_PRIORITY - 2); t1.start(); t2.start(); try { Thread.sleep(1000); } catch (Exception e) { } t1.stop(); t2.stop(); try { t1.t.join(); t2.t.join(); } catch (Exception e) { } System.out.println("低優先順序線程" +t2.click); System.out.println("高優先順序線程" +t1.click); }}class MyThread implementsRunnable{ int click = 0; Thread t; private volatile boolean running = true; public MyThread(int p){ t = new Thread(this); t.setPriority(p); } @Override public void run() { while (running) { click++; } } public void stop() { running = false; } public void start() { t.start(); }}
該程式的輸出結果與CPU有關,運行本程式還能看出CPU的速度。
2)、sleep睡眠方法
線程sleep()是一個靜態方法,它強制線程進入睡眠狀態。線上程中使用sleep()方法是非常必要的,它起到使線程停頓的作用。Sleep()方法可能拋出InterruptedException異常,在使用sleep()方法時一定要進行異常處理。
public class test{ public static void main(String[] str) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); MyThread3 t3 = new MyThread3(); t1.start(); t2.start(); t3.start(); }} class MyThread1 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("●"); try { Thread.sleep(1000); } catch (Exception e) { System.err.println("異常:" + e); } } }} class MyThread2 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("■"); try { Thread.sleep(1000); } catch (Exception e) { System.err.println("異常:" + e); } } }} class MyThread3 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("▲"); try { Thread.sleep(1000); } catch (Exception e) { System.err.println("異常:" + e); } } }}
我們可以看到運行結果還是不完全確定的,sleep()方法也只是讓每個線程都有啟動並執行機會。
3)、join加入方法
Join()方法使一個線程1跟在當前啟動並執行線程2的後面運行,當線程1運行完後再繼續運行線程2。我們先來看一個例子:
public class test{ public static void main(String[] str) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); MyThread3 t3 = new MyThread3(); t2.mt1 = t1; t1.start(); t2.start(); t3.start(); }} class MyThread1 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("●"); try { Thread.sleep(5000); } catch (Exception e) { System.err.println("異常:" + e); } } }} class MyThread2 extendsThread{ public MyThread1 mt1; @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("■"); try { Thread.sleep(5000); } catch (Exception e) { System.err.println("異常:" + e); } if (i == 10) { try { mt1.join(); } catch (Exception e) { System.err.println("異常:" + e); } } } }} class MyThread3 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.println("▲"); try { Thread.sleep(5000); } catch (Exception e) { System.err.println("異常:" + e); } } }}
我們可以看到運行結果為:
●▲■●■▲▲●■▲●■▲■●●▲■●■▲●■▲■▲●■●▲▲●■● (i=10) ▲▲●▲●▲●▲●▲●▲●●▲●▲■■■■■■■■■成功構建 (總時間: 29 秒)
當i = 10後,我們看到只列印出三角和實心圓,即線程t1和t3,當t1運行結束後,我們看到又列印出了正放心,即t2。
4)、yield讓步方法
yield()方法使當前啟動並執行線程回到可運行狀態,狀態的問題將在第五小節講到。Yield()方法的作用是讓有相同優先順序的線程獲得運行機會。yield()方法通常被稱為讓步方法,但它往往達不到讓步的目的,因為它只是讓當前線程回到可啟動並執行狀態,很可能啟動並執行線程又會是這一個。下面的例子使用預設優先順序講解yield()方法:
public class test{ public static void main(String[] str) { MyThread1 t1 = new MyThread1(); MyThread2 t2 = new MyThread2(); MyThread3 t3 = new MyThread3(); t1.start(); t2.start(); t3.start(); }} class MyThread1 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("●"); Thread.yield(); } }} class MyThread2 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("■"); Thread.yield(); } }} class MyThread3 extendsThread{ @Override public void run() { for (int i = 0; i < 20; i++) { System.out.print("▲"); } }}
我們對t1和t2執行yield()方法,執行結果會先列印出一連串的三角形,即t3。但讓步沒有完全成功,在三角形中仍然還有正方形和圓形:
■●▲▲▲▲▲▲▲●■●▲▲■▲▲▲▲▲▲▲▲▲▲▲●■■●■●■●●■●●■●●●■●■●■●■●■●■●■●■■■■成功構建 (總時間: 0 秒)