Java多線程(全)學習筆記(上)

一.線程的建立和啓動

java使用Thread類表明線程,全部的線程對象都必須是Thread類或其子類的實例。每條線程的做用是完成必定的任務,實際上就是執行一段程序流(一段順序流的代碼)。Java使用run方法來封裝這樣一段程序。 java

1.繼承Thread類建立線程類 linux

/**繼承Thread來建立線程類*/  
public class FirstThread extends Thread {  
private int i;  
//重寫run方法,run方法的方法體就是線程執行體  
public void run() {  
for(;i<10;i++){  
System.out.println(this.getName()+":"+i);  
}  
}  
public static void main(String []args){  
for(int i=0;i<20;i++){  
System.out.println(Thread.currentThread().getName()+"             .."+i);  
if(i==10){  
System.out.println("--------------------------------------------");  
new FirstThread().start();  
new FirstThread().start();  
System.out.println("---------------------------------------------");  
}  
}  
}  
}  
結果:紅色部分每次運行都不一致,由於多線程也是併發的  
main             ..0  
main             ..1  
main             ..2  
main             ..3  
main             ..4  
main             ..5  
main             ..6  
main             ..7  
main             ..8  
main             ..9  
main             ..10  
--------------------------------------------  
Thread-0:0  
---------------------------------------------  
Thread-1:0  
Thread-1:1  
Thread-1:2  
Thread-1:3  
Thread-0:1  
Thread-1:4  
Thread-1:5  
main             ..11  
Thread-1:6  
Thread-1:7  
Thread-1:8  
Thread-1:9  
Thread-0:2  
Thread-0:3  
main             ..12  
main             ..13  
......



總結 :從上面結果能夠看出Thread-0和Thread-1兩條線程輸出的i變量都不連續(注意:i變量是FirestThread的實例屬性,而不是局部變量,但由於程序每次建立線程都會建立一個FirstThread對象,因此Thread-0和Thread-1不能共享該實例屬性)。 多線程

使用繼承Thread類的方法來建立線程類,多條線程之間沒法共享線程類的實例變量。 併發

2.實現Runnable接口建立線程類

public class SecondThread implements Runnable {  
private int i;  
public void run() {  
for(;i<20;i++){  
System.out.println(Thread.currentThread().getName()+":"+i);  
}  
}  
public static void main(String [] args){  
for(int i=0;i<20;i++){  
System.out.println(Thread.currentThread().getName()+"             .."+i);  
if(i==10){  
SecondThread st=new SecondThread();  
//經過new Thread( Runable target,String name)來建立新線程  
new Thread(st,"線程1").start();  
new Thread(st,"線程2").start();  
}  
}  
}  
結果:紅色部分每次運行都不一致,由於多線程也是併發的  
main             ..0  
main             ..1  
main             ..2  
main             ..3  
main             ..4  
main             ..5  
main             ..6  
main             ..7  
main             ..8  
main             ..9  
main             ..10  
--------------------------------------------  
線程1:0  
--------------------------------------------  
線程1:1  
線程2:1  
線程2:3  
main             ..11  
線程2:4  
線程2:5  
線程2:6  
線程1:2  
線程2:7  
線程2:9  
線程2:10  
線程2:11  
線程2:12  
線程2:13  
main             ..12  
線程2:14  
線程2:15  
線程2:16  
線程2:17  
線程1:8  
線程2:18  
main             ..13  
main             ..14  
線程1:19  
main             ..15  
main             ..16  
main             ..17  
。。。。



總結:根據源代碼中Thread類構造方法  Ruanalbe接口對象target只能做爲參數傳遞到Thread構造方法中,因此多個線程能夠共用一個Runnable對象,由於都用同一個Runnable對象因此在Runnable實現類的實例變量也能夠共享了。 ide

因此Runable很是適合多個相同線程來處理同一份資源的狀況。 測試

二.線程的生命週期

   1.New新建 :當線程被建立時,該線程處於新建狀態,此時它和其餘java對象同樣,僅僅由Java虛擬機爲其分配了內存,並初始化了其成員變量的值。(此時的線程沒有表現出任何表現出任何線程的動態特徵,程序也不會執行線程的線程執行體)new Thread()||new Thread(Runnable target,String name)。 this

   2.Runnable就緒:就緒也就是說啓動線程,可是啓動線程使用start方法,而不是run方法!永遠不要調用線程對象的run()方法!調用start方法來啓動線程,系統會將該run方法當成線程執行體來處理。若是直接調用線程對象的run方法。則run方法會當即執行,且在這個run方法的執行體未執行結束前其餘線程沒法併發執行(即系統會將run方法當作一個普通對象的普通方法,而不是線程執行體對待) spa

      附1:若是有一個主線程,一個子線程。當根據邏輯代碼該調用子線程時不必定會當即調用,爲了想在子線程start()後當即調用子線程,能夠考慮使用Thread.sleep(1),這樣會讓當前線程(主線程)睡眠1毫秒,由於cpu在這1毫秒中是不會休息的,這樣就會去執行一條處於就緒狀態的線程。 線程

      附2:不能對已經處於就緒狀態的線程,再次使用start() unix

3.Running 運行:當處於就緒狀態時,該線程得到cpu,執行體開始運行,就處於運行狀態了。

4.Blocked 阻塞:線程不可能一直處於運行狀態(線程執行體足夠短,瞬間就能夠完成的線程排除),線程會在運行過程當中須要被中斷,由於是併發,目的是會讓其餘線程得到執行的機會,線程的調度細節取決於OS採用的策略。(搶佔式調度xp win7 linux unix..)。若是是一些特殊的小型設備可能採用 協做式調度(只有線程本身調用它的sleep()或yield()纔會放棄所佔用的資源)。


5.Dead死亡:根據上圖所示。測試測試某條線程是否已經死亡,能夠調用線程對象的isAlive()方法,當線程處於就緒,運行,阻塞時,返回true。線程處於新建,死亡時返回false

不能對已經死亡的線程調用start()方法使它從新啓動,死亡就是死亡,是不能再次做爲線程執行的。

當主線程結束時候,其餘線程不受任何影響,並不會隨之結束。一旦子線程啓動起來後,它就擁有和主線程相同的地位,它不會受到主線程的影響。

三.控制線程

1.join線程:

讓一個線程等待另外一個線程完成的方法:join()。當在某個程序執行流中調用其餘線程的join()方法,那該執行流對應的線程就會阻塞,知道被join()加入的join線程完成爲止。join方法一般有使用線程的程序調用,將大問題劃分紅許多小問題,每一個小問題分配一個線程。當全部的小問題都獲得處理後,再調用 主線程來進一步操做(Thread t=new Thread();t.start();t.join簡單來講就是加入到t線程。等t線程執行完成後纔會返回出來執行線程。

     Join方法有三種重用形式:

           Join():等待被join的線程執行完成

           Join(long millis):等待join線程的時間最長爲millis毫秒,若是在這個時間內,被join的線程尚未執行結束則再也不等待)

           Joinlong millisint nanos)千分之一毫秒(不用)

Code

public class JoinThread implements Runnable{  
@Override  
public void run() {  
for(int i=0;i<5;i++){  
System.out.println(Thread.currentThread().getName()+":"+i);  
}  
}  
public static void main(String [] args) throws InterruptedException{  
//實例化一個Runnable  
JoinThread jt=new JoinThread();  
//建立一個線程  
new Thread(jt).start();  
for(int i=0;i<10;i++){  
if(i==3){  
Thread th=new Thread(jt);  
//啓動第二個線程  
th.start();  
//main的線程中調用了th線程的join方法  
//讓第二個線程執行完成後再執行main  
th.join();  
}  
System.out.println(Thread.currentThread().getName()+":"+i);  
}  
}  
}  
結果:  
Thread-0:0  
Thread-0:1  
Thread-0:2  
main:0  
main:1  
Thread-0:3  
main:2  
Thread-0:4  
Thread-1:0  
Thread-1:1  
Thread-1:2  
Thread-1:3  
Thread-1:4  
main:3  
main:4  
main:5  
main:6  
main:7  
main:8  
main:9



2.後臺線程:

Code:

public class DaemonThread implements Runnable{  
@Override  
public void run() {  
for(int i=0;i<100;i++){  
System.out.println(Thread.currentThread().getName()+":"+i);  
}  
}  
public static void main(String [] args){  
//要將前臺線程轉換成後臺線程,須要在該線程剛新建還未start()以前轉換。main線程也是前臺線程  
//全部前臺線程死亡時,後臺線程也就隨之死亡。  
DaemonThread dt=new DaemonThread();  
Thread td=new Thread(dt,"線程1");  
System.out.println("main方法是不是後臺線程"+Thread.currentThread().isDaemon());  
System.out.println("td線程最初是不是後臺線程"+td.isDaemon());  
//指定td爲後臺線程  
td.setDaemon(true);  
System.out.println("td線程執行setDaemon方法後是不是後臺線程"+td.isDaemon());  
//就緒啓動後臺線程  
td.start();  
for(int i=0;i<5;i++){  
System.out.println(Thread.currentThread().getName()+" "+i);  
}  
}  
}  
結果:只要前臺線程結束,後臺線程也會隨之結束,並非立刻結束  
main方法是不是後臺線程false  
td線程最初是不是後臺線程false  
td線程執行setDaemon方法後是不是後臺線程true  
main 0  
main 1  
線程1:0  
線程1:1  
main 2  
線程1:2  
線程1:3  
main 3  
線程1:4  
線程1:5  
main 4  
線程1:6  
線程1:7  
線程1:8  
線程1:9  
線程1:10  
線程1:11  
線程1:12  
線程1:13



3.線程睡眠:sleep

/** 
 * 線程睡眠:sleep有兩種重載形式: 
 * static void sleep(long millis) 
 * static void sleep(long millis,int nanos) 
 * 
 */  
public class SleepThread {  
public static void main(String [] args) throws InterruptedException{  
for(int i=0;i<5;i++){  
System.out.println("線程:"+Thread.currentThread().getName()+"當前時間:"+new Date());  
//讓當前線程暫停2秒  
Thread.sleep(2000);  
}  
}  
}  
結果:  
線程:main當前時間:Fri Nov 04 18:51:33 CST 2011  
線程:main當前時間:Fri Nov 04 18:51:35 CST 2011  
線程:main當前時間:Fri Nov 04 18:51:37 CST 2011  
線程:main當前時間:Fri Nov 04 18:51:39 CST 2011  
線程:main當前時間:Fri Nov 04 18:51:41 CST 2011



4.線程讓步(yield

/** 
 * yield()方法是一個和sleep方法有點相似的靜態方法。yield也可讓當前正在執行的線程暫停 
 * 但它不會阻塞該線程,它只是將該線程轉入就緒狀態。yield只是讓當前線程暫停一下子,讓系統的 
 * 調度器從新調度一次(徹底可能的狀況是:當一個線程調用了yield方法暫停以後,線程調度器又立刻 
 * 將其調度出來從新執行。) 
 * 實際上,當前線程調用了yield方法後,只有優先級和當前線程相同,甚至優先級高於當前線程的處於 
 * 就緒狀態的線程纔會得到執行機會。 
 * 
 */  
public class YieldThread implements Runnable{  
@Override  
public void run() {  
for(int i=0;i<50;i++){  
System.out.println(Thread.currentThread().getName()+":"+i);  
if(i==20){  
Thread.yield();  
}  
}  
}  
public static void main(String [] args){  
//啓動第一條子線程  
Thread td1=new Thread(new YieldThread(),"線程1");  
//最高級  
//td1.setPriority(Thread.MAX_PRIORITY);  
//啓動第二條子線程  
Thread td2=new Thread(new YieldThread(),"線程2");  
//最低級  
td2.setPriority(Thread.MIN_PRIORITY);  
td1.start();  
td2.start();  
System.out.println(Thread.currentThread().getName());  
}  
}



總結:sleepyield區別

A.sleep方法暫停當前線程後,會給其餘線程執行機會,不會理會其餘線程的優先級。而yield只會給優先級>=當前優先級的線程執行機會

B.Sleep方法會將線程轉入阻塞狀態,知道通過阻塞時間纔會轉入就緒狀態。而yield是不會將線程轉入阻塞狀態的,它只是強制當前線程進入就緒狀態。

C.Sleep會拋出InterruptedException異常。而yield沒有聲明任何異常

D.Sleep方法比yield方法有更好的移植性。

E.一般不依靠yield來控制併發線程控制

相關文章
相關標籤/搜索