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

資源下載地址:http://download.csdn.net/detail/cloudyxuq/3763101

一.線程的創建和啓動

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

1.繼承Thread類創建線程類

/**繼承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實現類的實例變量也可以共享了。

所以Runable非常適合多個相同線程來處理同一份資源的情況。

二.線程的生命週期

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

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

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

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

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來控制併發線程控制




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章