Java里的线程控制
這篇文章接著上篇文章<<java 線程簡(jiǎn)介>> 寫的.
http://blog.csdn.net/nvd11/article/details/19118683
上一篇文章提到,? java程序猿可以利用類Thread或其接口Runnable開啟一條新線程.
但開啟一條新線程之后, 不能任由它不管啊.
其實(shí)java有很多方法讓程序猿控制線程的執(zhí)行過程.
一, 線程的三種狀態(tài)切換
一個(gè)線程由線程類Thread或其派生類的start()啟動(dòng).
啟動(dòng)后的線程具有3種狀態(tài).
1.1 就緒狀態(tài)
注意, 1個(gè)線程t被t.start()函數(shù)啟動(dòng)后, 并不是立即可以被cpu執(zhí)行. 而是進(jìn)入了就緒狀態(tài).
在就緒狀態(tài)中的線程代表了有了cpu執(zhí)行的資格. 由于想搶占cpu被執(zhí)行的線程有很多. cpu并不一定立即執(zhí)行t對(duì)應(yīng)的線程.
實(shí)際上, 一般情況下在1個(gè)時(shí)間點(diǎn), cpu只會(huì)執(zhí)行1個(gè)線程, 但是這個(gè)cpu會(huì)不斷跳到另1個(gè)線程去執(zhí)行它. 前提是這個(gè)"另1個(gè)進(jìn)程是"就緒狀態(tài).
1.2 運(yùn)行狀態(tài)
相對(duì)地, cpu當(dāng)前執(zhí)行進(jìn)程所在的狀態(tài)就是執(zhí)行狀態(tài).
一般來講, 一個(gè)程序運(yùn)行中同1個(gè)時(shí)間點(diǎn)只會(huì)有1條線程處于運(yùn)行狀態(tài).????
但是cpu會(huì)不斷地切換所執(zhí)行的線程.? 一旦cpu切換到另1個(gè)線程執(zhí)行. 原來運(yùn)行的線程就會(huì)從運(yùn)行狀態(tài)切換到就緒狀態(tài).?
這種行為我們就稱為cpu的調(diào)度.
1.3 阻塞狀態(tài).
一旦1個(gè)線程遇到阻塞事件(例如sleep()函數(shù)), (無論它原來是在運(yùn)行狀態(tài)還是就緒狀態(tài)),就會(huì)進(jìn)入阻塞狀態(tài).
處于阻塞狀態(tài)的線程無法被cpu調(diào)度, 也就是無法被cpu執(zhí)行(進(jìn)入運(yùn)行狀態(tài)).
直接阻塞接觸后, 該線程返回就緒狀態(tài).
簡(jiǎn)單圖示如下:
1.4 現(xiàn)實(shí)例子
假如喲有A, B, C 三個(gè)人都想使用另1個(gè)人D都電腦上網(wǎng),? 但是D只有1臺(tái)電腦。
然后D就讓A B C 三人首先坐在廳里都長(zhǎng)沙發(fā)上, 然后D挑選1個(gè)人進(jìn)房間使用電腦。
每隔一段時(shí)間D會(huì)把使用電腦都人趕回廳里都沙發(fā)上, 然后再?gòu)纳嘲l(fā)上挑1個(gè)人進(jìn)去使用電腦。
那么3個(gè)人就都有機(jī)會(huì)使用電腦, 令網(wǎng)絡(luò)上覺得A B C3個(gè)人有電腦都假象。
1.那么在房間使用電腦都人就相對(duì)于多線程的運(yùn)行狀態(tài)。
2.坐在沙發(fā)的人就相當(dāng)于多線程的就緒狀態(tài), 他們都有機(jī)會(huì)被D選中使用電腦.
3.如過A突然肚子痛(阻塞事件)離開了沙發(fā), 那么A就相當(dāng)于進(jìn)入了線程的阻塞狀態(tài), 除非A再次返回沙發(fā)上, 才會(huì)有資格被D選中。
二, 線程的優(yōu)先級(jí)設(shè)置
一般來講, 假如有兩條線程再執(zhí)行, cpu是會(huì)隨機(jī)切換執(zhí)行的。
但是實(shí)際上線程也有重要性的區(qū)別,我們可以利用優(yōu)先級(jí)別設(shè)置來控制某1個(gè)線程更有機(jī)會(huì)搶占cpu資源。
Java提供1個(gè)線程調(diào)度器來監(jiān)控程序中啟動(dòng)后進(jìn)入就緒狀態(tài)都所有線程。
線程調(diào)度器挑選執(zhí)行線程的機(jī)制受到線程優(yōu)先級(jí)的影響。
2.1 類Thread 的3個(gè)靜態(tài)常量成員
線程的優(yōu)先級(jí)用數(shù)字表示, 范圍從1到10, 1個(gè)線程都默認(rèn)優(yōu)先級(jí)是5。
這個(gè)數(shù)字越大表示線程都優(yōu)先級(jí)越高
類Thread還提供了3個(gè)靜態(tài)常量成員, 提供給程序猿,可以代替數(shù)字來使用。
他們分別是
Thread.MIN_PRIORITY=1
Thread.MAX_PRIORITY=10
Thread.NORM_PRIORITY=5
注意, 這個(gè)3個(gè)成員都是static final的. 代表都是它們都屬于類本身,而且不能被賦值。
2.2 線程對(duì)象獲取和設(shè)置優(yōu)先級(jí)的方法。
2.2.1 int getPriority();
調(diào)用這個(gè)方法可以獲得對(duì)應(yīng)線程對(duì)象都優(yōu)先級(jí)。 例如 t.getPriority() 返回都就是線程對(duì)象t都當(dāng)前優(yōu)先級(jí)。
2.2.2 void setPriority(int newPriority)
線程對(duì)象可以調(diào)用這個(gè)方法來改變本身都優(yōu)先級(jí).
注意,參數(shù)范圍必須再1-10, 否則會(huì)拋出java.lang.IllegalArgumentException 異常。
2.3 設(shè)置優(yōu)先級(jí)都一個(gè)例子
package Thread_kng.Td_priority_kng;class M_thrd_6 implements Runnable{public void run(){int i;Thread cur_thd = Thread.currentThread();for (i=1; i<101; i++){System.out.printf("Thread %s: priority is %d, i is %d\n", cur_thd.getName(),cur_thd.getPriority(),i);}} } public class Td_priority_1{public static void f(){M_thrd_6 s = new M_thrd_6();Thread t1 = new Thread(s);Thread t2 = new Thread(s);t1.setName("T1");t2.setName("T2");t1.start();t1.setPriority(Thread.MIN_PRIORITY); //set the priority to 1t2.setPriority(Thread.NORM_PRIORITY + 3); //set the priority to 8t2.start();} }看上面的例子:
我利用實(shí)驗(yàn)Runnable 接口的1個(gè)對(duì)象構(gòu)造兩個(gè)子線程。
在run方法里。
這兩個(gè)子線程(T1, T2)循環(huán)100次把i輸出到屏幕上, 并順便輸出線程的名字,和線程都當(dāng)前優(yōu)先級(jí)(getPriority())都輸出到屏幕。
看例子下面的f()函數(shù), 首先執(zhí)行的是t1. 再執(zhí)行的是t2.
但是t1的優(yōu)先級(jí)別調(diào)低了, t2的優(yōu)先級(jí)別調(diào)高了。
結(jié)果就是T2比T1先執(zhí)行完成。
執(zhí)行結(jié)果:
2.4 優(yōu)先級(jí)對(duì)線程調(diào)度的真正影響
見到結(jié)果中,雖然T2的優(yōu)先級(jí)比T1高得多, 但是T2和T1實(shí)際上還是不斷被切換執(zhí)行的。
2.4.1 時(shí)間片論算法。
所謂時(shí)間片論算法就是指java中1個(gè)線程處于執(zhí)行狀態(tài)的單次時(shí)間是一定的。
所以即使T2的優(yōu)先級(jí)比T1高, 并不是指T2處于單次執(zhí)行狀態(tài)的時(shí)間比較長(zhǎng)。
假如單次執(zhí)行狀態(tài)的時(shí)間是x毫秒,
而優(yōu)先級(jí)別高的線程被選中的機(jī)率相對(duì)更高。
那么單位時(shí)間內(nèi), T2進(jìn)入執(zhí)行狀態(tài)的次數(shù)會(huì)比T1高。?
實(shí)際上, 這個(gè)單次執(zhí)行時(shí)間比輸出100次i的時(shí)間小得多, 所以上面運(yùn)行例子會(huì)見到T1 和 T2不斷切換執(zhí)行。
2.4.2 實(shí)際開發(fā)中,java并不單純依賴優(yōu)先級(jí)別來決定線程執(zhí)行次序。
也就是講, 優(yōu)先級(jí)別只是影響cpu調(diào)度的其中1個(gè)因數(shù)。
還有如下其他因數(shù):
1. 突發(fā)事件, 例如打印機(jī)的打印線程發(fā)現(xiàn)打印機(jī)的紙張用完, 那么系統(tǒng)就會(huì)馬上執(zhí)行打印線程的警告機(jī)制。
2. 線程執(zhí)行時(shí)間, 通常會(huì)把消耗資源小,執(zhí)行快的線程放在前面運(yùn)行。
3. 最長(zhǎng)等待時(shí)間, 一個(gè)線程即使優(yōu)先級(jí)別很少, 但是超過了一段等待時(shí)間后, 會(huì)被強(qiáng)制進(jìn)入執(zhí)行狀態(tài)。
2.4 現(xiàn)實(shí)例子
舉回上面A B C三個(gè)人上網(wǎng)的時(shí)間, 優(yōu)先級(jí)別就是A B C三個(gè)人跟D的個(gè)人關(guān)系了。
高優(yōu)先級(jí)別的人每次再就緒狀態(tài)中被D選中的機(jī)率更大, 但是每次進(jìn)入房間上網(wǎng)的時(shí)間還是一樣的。
三, 線程控制的常用方法
下面開始介紹線程的常用方法, 也是面試中問得比較多的地方:
3.1 sleep()
sleep(int n) 是1個(gè)常用的線程函數(shù),參數(shù)s代表的是毫秒數(shù)字,他的作用是令線程停止執(zhí)行并進(jìn)入阻塞狀態(tài)n毫秒。
在這n毫秒內(nèi), 這個(gè)線程不能被cpu執(zhí)行。
過了n毫秒后, 這個(gè)線程重新進(jìn)入就緒狀態(tài),但是不代表這個(gè)線程能馬上被cpu執(zhí)行。
上面說過了, 就緒狀態(tài)的線程想要執(zhí)行還需要取決于cpu的調(diào)度。
它在線程基類Thread中定義如下:
public static void sleep(long millis,
???????????????????????? int nanos)
????????????????? throws InterruptedException
??? 在指定的毫秒數(shù)加指定的納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠(暫停執(zhí)行),此操作受到系統(tǒng)計(jì)時(shí)器和調(diào)度程序精度和準(zhǔn)確性的影響。該線程不丟失任何監(jiān)視器的所屬權(quán)。
??? 參數(shù):
??????? millis - 以毫秒為單位的休眠時(shí)間。
??????? nanos - 要休眠的另外 0-999999 納秒。
??? 拋出:
??????? IllegalArgumentException - 如果 millis 值為負(fù)或 nanos 值不在 0-999999 范圍內(nèi)。
??????? InterruptedException - 如果任何線程中斷了當(dāng)前線程。當(dāng)拋出該異常時(shí),當(dāng)前線程的中斷狀態(tài) 被清除
需要注意的是: sleep函數(shù)會(huì)拋出異常, 而且這個(gè)異常是非RuntimeException, 所以必須進(jìn)行捕捉。
關(guān)于異常的文章里講過, 捕捉要不進(jìn)行try catch, 要不在函數(shù)定義中使用throws.
但是sleep()函數(shù)最終都是卸載線程類的run()方法里面的。 而基類Thread的run()方法是不拋出任何異常的。 所以由于多態(tài)的存在,重寫的run()的方法不能throws任何異常.
所以一般在使用sleep函數(shù)時(shí),必須使用try catch
下面是1個(gè)例子:
package Thread_kng.Td_ctrl;class M_thrd_7 implements Runnable{public void run(){java.text.DateFormat d1 = java.text.DateFormat.getDateTimeInstance();java.util.Date now;Thread cur_thd = Thread.currentThread();int i;for (i=0; i<10; i++){now = new java.util.Date();System.out.printf("Thread %s: i is %d, time is %s\n",cur_thd.getName(), i, d1.format(now));try{cur_thd.sleep(3000); //sleep 3 seconds, must be put into the try{}}catch(Exception e){}}} }public class Td_ctrl_1{public static void f(){M_thrd_7 s = new M_thrd_7();Thread t1 = new Thread(s);t1.setName("T1");t1.start();Thread t2 = new Thread(s);t2.setName("T2");t2.start();} }這個(gè)例子的線程業(yè)務(wù)很簡(jiǎn)單, 就是把i從9輸出到0, 但是每輸出1次暫停3000毫秒。
需要注意的是,sleep()函數(shù)是類Thread的一個(gè)成員方法, 所以使用sleep()方法的前提是1個(gè)Thread類或其派生類的一個(gè)對(duì)象。
在下面的f()方法中,利用1個(gè)對(duì)象創(chuàng)建了兩個(gè)線程t1.t2并啟動(dòng)。
結(jié)果就是t1 和 t2兩條線程都是每3秒在屏幕輸出一次信息:
輸出結(jié)果:
Thread T1: i is 0, time is Feb 17, 2014 2:42:10 PM Thread T2: i is 0, time is Feb 17, 2014 2:42:10 PM Thread T1: i is 1, time is Feb 17, 2014 2:42:13 PM Thread T2: i is 1, time is Feb 17, 2014 2:42:13 PM Thread T1: i is 2, time is Feb 17, 2014 2:42:16 PM Thread T2: i is 2, time is Feb 17, 2014 2:42:16 PM Thread T1: i is 3, time is Feb 17, 2014 2:42:19 PM Thread T2: i is 3, time is Feb 17, 2014 2:42:19 PM Thread T1: i is 4, time is Feb 17, 2014 2:42:22 PM Thread T2: i is 4, time is Feb 17, 2014 2:42:22 PM Thread T1: i is 5, time is Feb 17, 2014 2:42:25 PM Thread T2: i is 5, time is Feb 17, 2014 2:42:25 PM Thread T1: i is 6, time is Feb 17, 2014 2:42:28 PM Thread T2: i is 6, time is Feb 17, 2014 2:42:28 PM Thread T1: i is 7, time is Feb 17, 2014 2:42:31 PM Thread T2: i is 7, time is Feb 17, 2014 2:42:31 PM Thread T1: i is 8, time is Feb 17, 2014 2:42:34 PM Thread T2: i is 8, time is Feb 17, 2014 2:42:34 PM Thread T1: i is 9, time is Feb 17, 2014 2:42:37 PM Thread T2: i is 9, time is Feb 17, 2014 2:42:37 PM gateman@TPEOS classes $sleep() 是1個(gè)靜態(tài)方法, 一般來講直接調(diào)用Thread.sleep(x),? 就可以令當(dāng)前執(zhí)行的線程暫停x毫秒. 并不需要獲得當(dāng)前的線程對(duì)象.
3.2 yield()
yield方法是基類Thread的另1個(gè)成員方法。
在中文JDK的解析是這樣的:
public static void yield()
暫停當(dāng)前正在執(zhí)行的線程對(duì)象,并執(zhí)行其他線程。
但是這個(gè)解釋并不準(zhǔn)確。
我們一般把yield方法簡(jiǎn)稱為線程讓步。
準(zhǔn)確的解析如下:
當(dāng)t線程執(zhí)行t.yield() 后,會(huì)馬上停止t的執(zhí)行狀態(tài), 并且令t退回到就緒狀態(tài)。
1. yield方法沒有參數(shù)。
2. yield 會(huì)令線程的執(zhí)行狀態(tài)終止。
3. yield 會(huì)令線程退回到就緒狀態(tài)。
4. 然后cpu還重新調(diào)度, 選擇一個(gè)線程進(jìn)入執(zhí)行狀態(tài),? 注意這個(gè)線程有可能是剛才yield退回就緒狀態(tài)的線程。
5. 所以java jdk api 中午的解析是不準(zhǔn)確的。并不是暫停線程, 并執(zhí)行其他線程, 而是暫停線程, 重新讓cpu調(diào)度。
也就是說1個(gè)線程執(zhí)行yield后被退回就緒狀態(tài), 如果它的優(yōu)先級(jí)別高, 它是有可能馬上被cpu重新執(zhí)行進(jìn)行執(zhí)行狀態(tài)的。
那么yield()的意義是什么呢, 本屌也不是很sure,? 但是如果1個(gè)線程,不斷循環(huán)執(zhí)行1個(gè)包含yield的方法, 那么每一次循環(huán)它都會(huì)讓步一次。
間接地令到這個(gè)線程比其他不含yield的線程更低。
下面是1個(gè)例子:
package Thread_kng.Td_ctrl;class M_thrd_8 extends Thread{public M_thrd_8(String name){super(name);}public void run(){int i;for (i=0; i<1000; i++){System.out.printf("Thread %s: i is %d",this.getName(), i);this.yield();}} }class M_thrd_9 extends Thread{public M_thrd_9(String name){super(name);}public void run(){int i;for (i=0; i<1000; i++){System.out.printf("\nThread %s: i is %d\n",this.getName(), i);//this.yield();}} }public class Td_yield_1{public static void f(){M_thrd_8 t1 = new M_thrd_8("T1");t1.start();M_thrd_9 t2 = new M_thrd_9("T2");t2.start();} }上面例子定義兩個(gè)基本相同的線程類, 都是循環(huán)把i 從0 輸出到 999。
但是t1 線程每1個(gè)循環(huán)都yield讓步一次, t2 線程沒有。
執(zhí)行時(shí), t2會(huì)比t1得到更多cpu資源。
如果這個(gè)兩個(gè)線程屬于不同進(jìn)程, 那么具有yield方法的進(jìn)程cpu占用率會(huì)稍低。
對(duì)于這個(gè)例子來說, 結(jié)果就是t2執(zhí)行得比t1快, 而且比單純地設(shè)置優(yōu)先級(jí)別明顯得多.
執(zhí)行結(jié)果:
3.3 sleep() 和 yield()的區(qū)別
我們還是舉回上面A B C三人用D的電腦上網(wǎng)的例子:
sleep()函數(shù)必須有個(gè)時(shí)間參數(shù), 加入線程A執(zhí)行了sleep(n),就相當(dāng)于A有事去廁所, 時(shí)間是n。 這段時(shí)間內(nèi)A處于阻塞狀態(tài), 無法被D選中去上網(wǎng)的。
過了n時(shí)間后, A回來到沙發(fā)上進(jìn)入就緒狀態(tài), 但是還是需要D的調(diào)度才能去上網(wǎng)。
而yield方法就相當(dāng)于正在上網(wǎng)的A被D拉回到沙發(fā)上, 然后重新等待D的調(diào)度, 但是有可能D還是選中A的, 也就是說A被拉回到沙發(fā)上, 但是馬上又可以去上網(wǎng)。
3.4 join()
join() 是基類Thread的另1個(gè)成員方法。
public final void join()
??????????????? throws InterruptedException
等待該線程終止。
詳細(xì)的定義是:
當(dāng)線程t 調(diào)用t.join() 時(shí), 暫停當(dāng)前線程的執(zhí)行, 除非t執(zhí)行完成了, 當(dāng)前線程繼續(xù)執(zhí)行。
注意當(dāng)前線程是執(zhí)行t.join()的線程。
現(xiàn)實(shí)例子:
又是A B C三人上網(wǎng)的例子, 假如B線程執(zhí)行A.join() , 也相當(dāng)于B告訴D,我離開一會(huì), 等A上完網(wǎng)時(shí)我就才回來上網(wǎng)。
注意,C不受影響哦, 實(shí)際上就是B 1個(gè)人暫時(shí)退出, A和C兩人簡(jiǎn)單切換上網(wǎng), 直至A上完網(wǎng), B才回來就緒狀態(tài)!
值得注意的是, join()類似sleep()方法, 都會(huì)拋出異常。
下面是1個(gè)join()方法的例子:
上面定義了兩個(gè)線程類, 實(shí)例化了3個(gè)對(duì)象t1, t2, t3, 而t3的run()方法里, 調(diào)用了t1.join()。
也就是說t1. t2都把i從0輸出到999
但是t3先輸出到500 就必須等t1完成, 才繼續(xù)輸出501 到 999
也就是說t3必須等t1完成才能執(zhí)行
但是t2是不受影響的。
執(zhí)行結(jié)果:
Thread T1: i is 981 Thread T1: i is 982 Thread T1: i is 983 Thread T1: i is 984 Thread T1: i is 985 Thread T1: i is 986 Thread T1: i is 987 Thread T1: i is 988 Thread T1: i is 989 Thread T1: i is 990 Thread T1: i is 991 Thread T1: i is 992 Thread T1: i is 993 Thread T1: i is 994 Thread T1: i is 995 Thread T1: i is 996 Thread T1: i is 997 Thread T1: i is 998 Thread T1: i is 999 Thread T2: i is 899 Thread T3: i is 501 Thread T3: i is 502 Thread T3: i is 503 Thread T3: i is 504 Thread T3: i is 505 Thread T3: i is 506 Thread T3: i is 507 Thread T3: i is 508 Thread T3: i is 509 Thread T3: i is 510 Thread T3: i is 511 Thread T3: i is 5123.5 wait() notify() notifyAll()
這個(gè)3個(gè)方法涉及同步的問題,? 我會(huì)在以后介紹java同步的博文里再講
總結(jié)
以上是生活随笔為你收集整理的Java里的线程控制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java 里的thread (线程)简介
- 下一篇: java同步机制简单介绍