這篇文章主要介紹了Java線程的生命週期和狀態控制,需要的朋友可以參考下
一、執行緒的生命週期
執行緒狀態轉換圖:
1、新狀態
用new關鍵字和Thread類別或其子類別建立一個執行緒物件後,該執行緒物件就處於新生狀態。處於新生狀態的執行緒有自己的記憶體空間,透過呼叫start方法進入就緒狀態(runnable)。
注意:不能對已經啟動的執行緒再次呼叫start()方法,否則會出現Javalang.IllegalThreadStateException異常。
2、就緒狀態
處於就緒狀態的執行緒已經具備了運行條件,但還沒有被分配到CPU,處於執行緒就緒隊列(儘管是採用隊列形式,事實上,稱它為可運行池而不是可運行隊列。因為cpu的調度不一定是按照先進先出的順序來調度的),等待系統為其分配CPU。等待狀態並不是執行狀態,當系統選定一個等待執行的Thread物件後,它就會從等待執行狀態進入執行狀態,系統挑選的動作稱為「cpu調度」。一旦獲得CPU,執行緒就進入運行狀態並自動呼叫自己的run方法。
提示:如果希望子執行緒呼叫start()方法後立即執行,可以使用Thread.sleep()方式使主執行緒睡眠一夥兒,請轉去執行子執行緒。
3、運行狀態
處於運作狀態的執行緒最為複雜,它可以變成阻塞狀態、就緒狀態和死亡狀態。處於就緒狀態的線程,如果獲得了cpu的調度,就會從就緒狀態變成運行狀態,執行run()方法中的任務。如果該執行緒失去了cpu資源,就會又從運作狀態變成就緒狀態。重新等待系統分配資源。也可以對在運作狀態的執行緒呼叫yield()方法,它就會讓出cpu資源,再次變成就緒狀態。
當發生以下情況是,執行緒會從運行狀態變成阻塞狀態:
①、執行緒呼叫sleep方法主動放棄所佔用的系統資源
②、執行緒呼叫一個阻塞式IO方法,在該方法返回之前,該執行緒被阻塞
③、執行緒試圖取得一個同步監視器,但更改同步監視器正被其他執行緒所持有
④、執行緒在等待某個通知(notify)
⑤、程式呼叫了執行緒的suspend方法將執行緒掛起。不過此方法容易導致死鎖,所以程式應該盡量避免使用此方法。
當執行緒的run()方法執行完,或被強制性地終止,例如出現異常,或是呼叫了stop()、desyory()方法等等,就會從運行狀態轉變為死亡狀態。
4、阻塞狀態
處於運行狀態的執行緒在某些情況下,如執行了sleep(睡眠)方法,或等待I/O設備等資源,將讓出CPU並暫時停止自己的運行,進入阻塞狀態。
在阻塞狀態的執行緒不能進入就緒佇列。只有當造成阻塞的原因消除時,如睡眠時間已到,或等待的I/O設備空閒下來,線程便轉入就緒狀態,重新到就緒隊列中排隊等待,被系統選中後從原來停止的位置開始繼續運行。
5、死亡狀態
當執行緒的run()方法執行完,或被強制性地終止,就認為它死去。這個線程物件也許是活的,但是,它已經不是一個單獨執行的線程。線一旦死亡,就不能復生。 如果在一個死去的執行緒上呼叫start()方法,會拋出java.lang.IllegalThreadStateException異常。
二、執行緒狀態的控制
Java提供了一些方便的方法用於會執行緒狀態的控制。
.
可以看到很多方法,已經標註為過時的,我們應該盡可能的避免使用它們,而應該專注於start()、interrupt()、join()、 sleep()、yield()等直接控制方法,和setDaemon()、setPriority()等間接控制方法。
1、線程睡眠——sleep
如果我們需要讓目前正在執行的執行緒暫停一段時間,並進入阻塞狀態,則可以透過呼叫Thread的sleep方法,從上面可以看到sleep方法有兩種重載的形式,但使用方法一樣。
例如,我們想要讓主執行緒每休眠100毫秒,然後再印出數字:
public class Test { public static void main(String[] args) throws InterruptedException { for(int i=;i<;i++){ System.out.println("main"+i); Thread.sleep(); } } }
可以明顯看到列印的數字在時間上有些許的間隔。
注意以下幾點問題
①、sleep是靜態方法,最好不要用Thread的實例物件呼叫它,因為它睡眠的始終是當前正在運行的線程,而不是呼叫它的線程對象,它只對正在運行狀態的線程對像有效。看下面的範例:
public class Test1 { public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()); MyThread myThread=new MyThread(); myThread.start(); myThread.sleep(1000);//这里sleep的就是main线程,而非myThread线程 Thread.sleep(10); for(int i=0;i<100;i++){ System.out.println("main"+i); } } }
②、Java執行緒調度是Java多執行緒的核心,只有良好的調度,才能充分發揮系統的效能,提升程式的執行效率。但不管程式設計師怎麼寫調度,只能最大限度的影響執行緒執行的次序,而不能做到精準控制。因為使用sleep方法之後,執行緒是進入阻塞狀態的,只有當睡眠的時間結束,才會重新進入到就緒狀態,而就緒狀態進入到運行狀態,是由系統控制的,我們不可能精準的去干涉它,所以如果呼叫Thread.sleep(1000)使得執行緒睡眠1秒,可能結果會大於1秒。
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread().start(); new MyThread().start(); } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 3; i++) { System.out.println(this.getName()+"线程" + i + "次执行!"); try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } } } }
看某一次的運行結果:
#1. Thread-0執行緒0次執行!
2. Thread-1執行緒0次執行!
3. Thread-1執行緒1次執行!
4. Thread-0執行緒1次執行!
5. Thread-0執行緒2次執行!
6. Thread-1執行緒2次執行!
可以看到,執行緒0先執行,然後執行緒1執行一次,又了一次。可以看到它並不是按照sleep的順序執行的。
2、執行緒讓步-yield
#yield()方法和sleep()方法有點相似,它也是Thread類別提供的一個靜態的方法,它也可以讓目前正在執行的執行緒暫停,讓出cpu資源給其他的執行緒。但是和sleep()方法不同的是,它不會進入到阻塞狀態,而是進入到就緒狀態。 yield()方法只是讓當前線程暫停一下,重新進入就緒的線程池中,讓系統的線程調度器重新調度器重新調度一次,完全可能出現這樣的情況:當某個線程調用yield()方法之後,執行緒調度器又將其調度出來重新進入到運行狀態執行。
實際上,當某個執行緒呼叫了yield()方法暫停之後,優先權與目前執行緒相同,或優先權比目前執行緒更高的就緒狀態的執行緒更有可能獲得執行的機會,當然,只是有可能,因為我們不可能精確的干涉cpu調度執行緒。
yield的用法:
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread("低级", 1).start(); new MyThread("中级", 5).start(); new MyThread("高级", 10).start(); } } class MyThread extends Thread { public MyThread(String name, int pro) { super(name);// 设置线程的名称 this.setPriority(pro);// 设置优先级 } @Override public void run() { for (int i = 0; i < 30; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); if (i % 5 == 0) Thread.yield(); } } }
關於sleep()方法和yield()方的差異如下:
void join(long millis)
当前线程等待该线程终止的时间最长为 millis 毫秒。 如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
void join(long millis,int nanos)
等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒。如果在millis时间内,该线程没有执行完,那么当前线程进入就绪状态,重新等待cpu调度
例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); thread.join(1);//将主线程加入到子线程后面,不过如果子线程在1毫秒时间内没执行完,则主线程便不再等待它执行完,进入就绪状态,等待cpu调度 for(int i=0;i<30;i++){ System.out.println(Thread.currentThread().getName() + "线程第" + i + "次执行!"); } } } class MyThread extends Thread { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); } } }
在这个例子中,在主线程中调用thread.join(); 就是将主线程加入到thread子线程后面等待执行。不过有时间限制,为1毫秒。
4、线程的优先级
每个线程执行时都有一个优先级的属性,优先级高的线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
每个线程默认的优先级都与创建它的父线程具有相同的优先级,在默认情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)和getPriority()方法来设置和返回一个指定线程的优先级,其中setPriority方法的参数是一个整数,范围是1~•0之间,也可以使用Thread类提供的三个静态常量:
MAX_PRIORITY =10
MIN_PRIORITY =1
NORM_PRIORITY =5
例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { new MyThread("高级", 10).start(); new MyThread("低级", 1).start(); } } class MyThread extends Thread { public MyThread(String name,int pro) { super(name);//设置线程的名称 setPriority(pro);//设置线程的优先级 } @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println(this.getName() + "线程第" + i + "次执行!"); } } }
从结果可以看到 ,一般情况下,高级线程更显执行完毕。
注意一点:虽然Java提供了10个优先级别,但这些优先级别需要操作系统的支持。不同的操作系统的优先级并不相同,而且也不能很好的和Java的10个优先级别对应。所以我们应该使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设定优先级,这样才能保证程序最好的可移植性。
5、守护线程
守护线程与普通线程写法上基本么啥区别,调用线程对象的方法setDaemon(true),则可以将其设置为守护线程。
守护线程使用的情况较少,但并非无用,举例来说,JVM的垃圾回收、内存管理等线程都是守护线程。还有就是在做数据库应用时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监控连接个数、超时时间、状态等等。
setDaemon方法的详细说明:
public final void setDaemon(boolean on)将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。
该方法必须在启动线程前调用。 该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
参数:
on - 如果为 true,则将该线程标记为守护线程。
抛出:
IllegalThreadStateException - 如果该线程处于活动状态。
SecurityException - 如果当前线程无法修改该线程。
/** * Java线程:线程的调度-守护线程 */ public class Test { public static void main(String[] args) { Thread t1 = new MyCommon(); Thread t2 = new Thread(new MyDaemon()); t2.setDaemon(true); //设置为守护线程 t2.start(); t1.start(); } } class MyCommon extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println("线程1第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } } class MyDaemon implements Runnable { public void run() { for (long i = 0; i < 9999999L; i++) { System.out.println("后台线程第" + i + "次执行!"); try { Thread.sleep(7); } catch (InterruptedException e) { e.printStackTrace(); } } } }
执行结果:
1. 后台线程第0次执行!
2. 线程1第0次执行!
3. 线程1第1次执行!
4. 后台线程第1次执行!
5. 后台线程第2次执行!
6. 线程1第2次执行!
7. 线程1第3次执行!
8. 后台线程第3次执行!
9. 线程1第4次执行!
10. 后台线程第4次执行!
11. 后台线程第5次执行!
12. 后台线程第6次执行!
13. 后台线程第7次执行!
从上面的执行结果可以看出:前台线程是保证执行完毕的,后台线程还没有执行完毕就退出了。
实际上:JRE判断程序是否执行结束的标准是所有的前台执线程行完毕了,而不管后台线程的状态,因此,在使用后台县城时候一定要注意这个问题。
守护线程的用途:
守护线程通常用于执行一些后台作业,例如在你的应用程序运行时播放背景音乐,在文字编辑器里做自动语法检查、自动保存等功能。Java的垃圾回收也是一个守护线程。守护线的好处就是你不需要关心它的结束问题。例如你在你的应用程序运行的时候希望播放背景音乐,如果将这个播放背景音乐的线程设定为非守护线程,那么在用户请求退出的时候,不仅要退出主线程,还要通知播放背景音乐的线程退出;如果设定为守护线程则不需要了。
6、如何结束一个线程
Thread.stop()、Thread.suspend、Thread.resume、Runtime.runFinalizersOnExit这些终止线程运行的方法已经被废弃了,使用它们是极端不安全的!想要安全有效的结束一个线程,可以使用下面的方法。
1、正常执行完run方法,然后结束掉
2、控制循环条件和判断条件的标识符来结束掉线程
比如说run方法这样写:
class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) break; i++; System.out.println(i); } } }
或者
class MyThread extends Thread { int i=0; boolean next=true; @Override public void run() { while (next) { if(i==10) next=false; i++; System.out.println(i); } } }
或者
class MyThread extends Thread { int i=0; @Override public void run() { while (true) { if(i==10) return; i++; System.out.println(i); } } }
只要保证在一定的情况下,run方法能够执行完毕即可。而不是while(true)的无线循环。
3、使用interrupt结束一个线程。
诚然,使用第2中方法的标识符来结束一个线程,是一个不错的方法,但是如果,该线程是处于sleep、wait、join的状态的时候,while循环就不会执行,那么我们的标识符就无用武之地了,当然也不能再通过它来结束处于这3种状态的线程了。
可以使用interrupt这个巧妙的方式结束掉这个线程。
我们看看sleep、wait、join方法的声明:
public final void wait() throws InterruptedException public static native void sleep(long millis) throws InterruptedException public final void join() throws InterruptedException
可以看到,这三者有一个共同点,都抛出了一个InterruptedException的异常。
在什么时候会产生这样一个异常呢?
每个Thread都有一个中断状状态,默认为false。可以通过Thread对象的isInterrupted()方法来判断该线程的中断状态。可以通过Thread对象的interrupt()方法将中断状态设置为true。
当一个线程处于sleep、wait、join这三种状态之一的时候,如果此时他的中断状态为true,那么它就会抛出一个InterruptedException的异常,并将中断状态重新设置为false。
看下面的简单的例子:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); } } class MyThread extends Thread { int i=1; @Override public void run() { while (true) { System.out.println(i); System.out.println(this.isInterrupted()); try { System.out.println("我马上去sleep了"); Thread.sleep(2000); this.interrupt(); } catch (InterruptedException e) { System.out.println("异常捕获了"+this.isInterrupted()); return; } i++; } } }
测试结果:
1. 1
2. false
3. 我马上去sleep了
4. 2
5. true
6. 我马上去sleep了
7. 异常捕获了false
可以看到,首先执行第一次while循环,在第一次循环中,睡眠2秒,然后将中断状态设置为true。当进入到第二次循环的时候,中断状态就是第一次设置的true,当它再次进入sleep的时候,马上就抛出了InterruptedException异常,然后被我们捕获了。然后中断状态又被重新自动设置为false了(从最后一条输出可以看出来)。
所以,我们可以使用interrupt方法结束一个线程。具体使用如下:
public class Test1 { public static void main(String[] args) throws InterruptedException { MyThread thread=new MyThread(); thread.start(); Thread.sleep(3000); thread.interrupt(); } } class MyThread extends Thread { int i=0; @Override public void run() { while (true) { System.out.println(i); try { Thread.sleep(1000); } catch (InterruptedException e) { System.out.println("中断异常被捕获了"); return; } i++; } } }
多测试几次,会发现一般有两种执行结果:
0
1
2
4. 中断异常被捕获了
或者
0
1
2
3
5. 中断异常被捕获了
这两种结果恰恰说明了 只要一个线程的中断状态一旦为true,只要它进入sleep等状态,或者处于sleep状态,立马回抛出InterruptedException异常。
第一种情况,是当主线程从3秒睡眠状态醒来之后,调用了子线程的interrupt方法,此时子线程正处于sleep状态,立马抛出InterruptedException异常。
第一種情況,是當主執行緒從3秒睡眠狀態醒來之後,呼叫了子執行緒的interrupt方法,此時子執行緒還沒有處於sleep狀態。然後再第3次while循環的時候,在此進入sleep狀態,立刻拋出InterruptedException異常。
以上是Java執行緒中的生命週期與狀態控制圖文詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!