1. 程式人生 > >多執行緒的使用,Java多執行緒學習(吐血超詳細總結)

多執行緒的使用,Java多執行緒學習(吐血超詳細總結)

多執行緒的使用

首先講一下程序和執行緒的區別:

    程序:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大的開銷,一個程序包含1--n個執行緒。

    執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。

    執行緒和程序一樣分為五個階段:建立、就緒、執行、阻塞、終止。

    多程序是指作業系統能同時執行多個任務(程式)。

    多執行緒是指在同一程式中有多個順序流在執行。

  在java中建立一個執行緒有兩種方法:

    ①實現java.lang.Runnable介面,重寫run()方法,啟動:new Thread(this).start()。

複製程式碼

 1 package com.thread;
 2 
 3 public class ThreadTest1 {
 4     public static void main(String[] args) {
 5         Runnable1 r = new Runnable1();
 6         //r.run();並不是執行緒開啟,而是簡單的方法呼叫
 7         Thread t = new Thread(r);//建立執行緒
 8         //t.run(); //如果該執行緒是使用獨立的 Runnable 執行物件構造的,則呼叫該 Runnable 物件的 run 方法;否則,該方法不執行任何操作並返回。
 9         t.start(); //執行緒開啟
10         for (int i = 0; i < 100; i++) {
11             System.out.println("main:"+i);
12         }
13     }
14 }
15 class Runnable1 implements Runnable{
16     public void run() {
17         for (int i = 0; i < 100; i++) {
18             System.out.println("Thread-----:"+i);
19         }
20     }
21 }

複製程式碼

  要注意的是:

    1.r.run()並不是啟動執行緒,而是簡單的方法呼叫。

    2.Thread也有run()方法,如果該執行緒是使用獨立的 Runnable 執行物件構造的,則呼叫該 Runnable 物件的 run 方法;否則,該方法不執行任何操作並返回。

    3.並不是一啟動執行緒(呼叫start()方法)就執行這個執行緒,而是進入就緒狀態,什麼時候執行要看CUP。

    ②繼承java.lang.Thread類,重寫run()方法。

複製程式碼

 1 package com.thread;
 2 
 3 public class TestThread2 {
 4     public static void main(String[] args) {
 5         Thread1 t = new Thread1();
 6         //t.run(); //這裡也不能直接呼叫方法
 7         t.start();
 8         for (int i = 0; i < 100; i++) {
 9             System.out.println("main:"+i);
10         }
11     }
12 }
13 
14 //儘量使用實現Runnnable介面,因為介面比較靈活
15 class Thread1 extends Thread{
16     @Override
17     public void run() {
18         for (int i = 0; i < 100; i++) {
19             System.out.println("Thread-----:"+i);
20         }
21     }
22 }

複製程式碼

雖然兩種方法都可行,但是最好還是用第一種方法,因為使用介面靈活性好,java中時單繼承、多實現。

Thread類中常用的方法有:

  ①sleep(long millis): 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)。

複製程式碼

 1 package com.thread;
 2 import java.util.Date;
 3 /**
 4  * sleep()指在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)。
 5  * @author Administrator
 6  *
 7  */
 8 public class SleepTest {
 9     public static void main(String[] args) {
10         Thread2 t = new Thread2();
11         t.start();
12         try {
13             Thread.sleep(10000); //主執行緒睡眠10秒鐘
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17         //主執行緒睡眠10秒鐘後結束t執行緒
18         //t.interrupt(); //這種結束方式比較粗暴,如果t執行緒打開了某個資源還沒來得及關閉也就是run方法還沒有執行完就強制結束執行緒,會導致資源無法關閉
19         //t.stop();也是結束某個執行緒,這種方式比interrupt()更粗暴
20         t.flag = false;
21     }
22 }
23 class Thread2 extends Thread{
24     boolean flag = true; //用這種方式結束執行緒很不錯,用一個變數控制run方法什麼時候不再執行,不會出現run方法沒有執行完畢就結束
25     @Override
26     public void run() { //run方法一結束,整個執行緒就終止了
27         while(flag){
28             System.out.println("---"+new Date()+"---");
29             try {
30                 sleep(1000);
31             } catch (InterruptedException e) {
32                 return;
33             }
34         }
35     }
36 }

複製程式碼

 

  ②join():指等待t執行緒終止。也可以理解為將t執行緒合併到當前執行緒來,等待t執行緒結束後再往下執行。相當於方法呼叫

複製程式碼

 1 package com.thread;
 2 
 3 import java.util.Date;
 4 
 5 /*
 6  * t.join()方法指等待t執行緒終止。也可以理解為將t執行緒合併到當前執行緒來,等待t執行緒結束後再往下執行。相當於方法呼叫
 7  */
 8 public class TestJoin {
 9     public static void main(String[] args) {
10         Thread t = new Thread3("abc");
11         t.start();
12         for (int i = 0; i < 20; i++) {
13             System.out.println("我是main執行緒");
14             if(i==10){
15                 try {
16                     t.join();
17                 } catch (InterruptedException e1) {
18                     // TODO Auto-generated catch block
19                     e1.printStackTrace();
20                 }
21             }
22             try {
23                 Thread.sleep(1000);
24             } catch (InterruptedException e) {
25                 e.printStackTrace();
26             }
27         }
28     }
29 }
30 class Thread3 extends Thread{
31     public Thread3(String s) { //給該執行緒取一個名字,用getName()方法可以去到該名字
32         super(s);
33     }
34     @Override
35     public void run() {
36         for (int i = 0; i < 20; i++) {
37             System.out.println("我是"+getName()+"執行緒");
38             try {
39                 sleep(1000);
40             } catch (InterruptedException e) {
41                 e.printStackTrace();
42             }
43         }
44     }
45 }

複製程式碼

執行該程式結果為:

複製程式碼

 1 我是main執行緒
 2 我是abc執行緒
 3 我是main執行緒
 4 我是abc執行緒
 5 我是main執行緒
 6 我是abc執行緒
 7 我是main執行緒
 8 我是abc執行緒
 9 我是main執行緒
10 我是abc執行緒
11 我是main執行緒
12 我是abc執行緒
13 我是main執行緒
14 我是abc執行緒
15 我是main執行緒
16 我是abc執行緒
17 我是main執行緒
18 我是abc執行緒
19 我是main執行緒
20 我是abc執行緒
21 我是main執行緒
22 我是abc執行緒
23 我是abc執行緒
24 我是abc執行緒
25 我是abc執行緒
26 我是abc執行緒
27 我是abc執行緒
28 我是abc執行緒
29 我是abc執行緒
30 我是abc執行緒
31 我是abc執行緒
32 我是main執行緒
33 我是main執行緒
34 我是main執行緒
35 我是main執行緒
36 我是main執行緒
37 我是main執行緒
38 我是main執行緒
39 我是main執行緒
40 我是main執行緒

複製程式碼

可以看到從第22行起就開始順序執行了,因為i=10的時候就將該形成合並了。

  ③yield():暫停當前正在執行的執行緒物件,並執行其他執行緒。

  ④setPriority(): 更改執行緒的優先順序。

    MIN_PRIORITY = 1
       NORM_PRIORITY = 5
           MAX_PRIORITY = 10

複製程式碼

 1 package com.thread;
 2 
 3 
 4 /*t.yield()暫停當前正在執行的執行緒物件,並執行其他執行緒。
 5  * 
 6  * MIN_PRIORITY 1
 7  * NORM_PRIORITY 5
 8  * MAX_PRIORITY 10
 9  */
10 public class TestYield {
11     public static void main(String[] args) {
12         Thread4 t1 = new Thread4("t1");
13         Thread4 t2 = new Thread4("t2");
14         t1.setPriority(Thread.MAX_PRIORITY);
15         t2.setPriority(Thread.MIN_PRIORITY);
16         System.out.println(t1.getPriority());
17         System.out.println(t2.getPriority());
18         t1.start();
19         t2.start();
20         
21     }
22 }
23 class Thread4 extends Thread{
24     public Thread4(String s) { 
25         super(s);
26     }
27     @Override
28     public void run() {
29         for (int i = 0; i < 1000; i++) {
30             System.out.println("我是"+getName()+"執行緒"+i);
31             if(i%10 == 0){
32                 yield();
33             }
34         }
35     }
36 }

複製程式碼

由於執行結果太長就沒有貼上來了,執行該程式,可以看到t1和t2兩個程序每次當i為10的倍數時都會讓給其他執行緒執行。

  ⑤interrupt():中斷某個執行緒,這種結束方式比較粗暴,如果t執行緒打開了某個資源還沒來得及關閉也就是run方法還沒有執行完就強制結束執行緒,會導致資源無法關閉

  要想結束程序最好的辦法就是用sleep()函式的例子程式裡那樣,線上程類裡面用以個boolean型變數來控制run()方法什麼時候結束,run()方法一結束,該執行緒也就結束了。

  ⑥還有很多的方法就不一一列舉了.........

  

  當然,多執行緒難點不在這些,多執行緒的難點在於多執行緒之間的協調。關於多執行緒的協調待續........

 

Java多執行緒學習(吐血超詳細總結)

         林炳文Evankaka原創作品。轉載請註明出處http://blog.csdn.net/evankaka

        寫在前面的話:此文只能說是java多執行緒的一個入門,其實Java裡頭執行緒完全可以寫一本書了,但是如果最基本的你都學掌握好,又怎麼能更上一個臺階呢?如果你覺得此文很簡單,那推薦你看看Java併發包的的執行緒池(Java併發程式設計與技術內幕:執行緒池深入理解),或者看這個專欄:Java併發程式設計與技術內幕。你將會對Java裡頭的高併發場景下的執行緒有更加深刻的理解。

目錄(?)[-]

  1. 一擴充套件javalangThread類
  2. 二實現javalangRunnable介面
  3. 三Thread和Runnable的區別
  4. 四執行緒狀態轉換
  5. 五執行緒排程
  6. 六常用函式說明
    1. 使用方式
    2. 為什麼要用join方法
  7. 七常見執行緒名詞解釋
  8. 八執行緒同步
  9. 九執行緒資料傳遞

        本文主要講了java中多執行緒的使用方法、執行緒同步、執行緒資料傳遞、執行緒狀態及相應的一些執行緒函式用法、概述等。在這之前,首先讓我們來了解下在作業系統中程序和執行緒的區別:

  程序:每個程序都有獨立的程式碼和資料空間(程序上下文),程序間的切換會有較大的開銷,一個程序包含1--n個執行緒。(程序是資源分配的最小單位)

  執行緒:同一類執行緒共享程式碼和資料空間,每個執行緒有獨立的執行棧和程式計數器(PC),執行緒切換開銷小。(執行緒是cpu排程的最小單位)

  執行緒和程序一樣分為五個階段:建立、就緒、執行、阻塞、終止。

  多程序是指作業系統能同時執行多個任務(程式)。

  多執行緒是指在同一程式中有多個順序流在執行。

在java中要想實現多執行緒,有兩種手段,一種是繼續Thread類,另外一種是實現Runable介面.(其實準確來講,應該有三種,還有一種是實現Callable介面,並與Future、執行緒池結合使用,此文這裡不講這個,有興趣看這裡Java併發程式設計與技術內幕:Callable、Future、FutureTask、CompletionService )

一、擴充套件java.lang.Thread類

這裡繼承Thread類的方法是比較常用的一種,如果說你只是想起一條執行緒。沒有什麼其它特殊的要求,那麼可以使用Thread.(筆者推薦使用Runable,後頭會說明為什麼)。下面來看一個簡單的例項

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. package com.multithread.learning;  
  2. /** 
  3.  *@functon 多執行緒學習 
  4.  *@author 林炳文 
  5.  *@time 2015.3.9 
  6.  */  
  7. class Thread1 extends Thread{  
  8.     private String name;  
  9.     public Thread1(String name) {  
  10.        this.name=name;  
  11.     }  
  12.     public void run() {  
  13.         for (int i = 0; i < 5; i++) {  
  14.             System.out.println(name + "執行  :  " + i);  
  15.             try {  
  16.                 sleep((int) Math.random() * 10);  
  17.             } catch (InterruptedException e) {  
  18.                 e.printStackTrace();  
  19.             }  
  20.         }  
  21.          
  22.     }  
  23. }  
  24. public class Main {  
  25.   
  26.     public static void main(String[] args) {  
  27.         Thread1 mTh1=new Thread1("A");  
  28.         Thread1 mTh2=new Thread1("B");  
  29.         mTh1.start();  
  30.         mTh2.start();  
  31.   
  32.     }  
  33.   
  34. }  

輸出:

 

A執行  :  0
B執行  :  0
A執行  :  1
A執行  :  2
A執行  :  3
A執行  :  4
B執行  :  1
B執行  :  2
B執行  :  3
B執行  :  4

再執行一下:

A執行  :  0
B執行  :  0
B執行  :  1
B執行  :  2
B執行  :  3
B執行  :  4
A執行  :  1
A執行  :  2
A執行  :  3
A執行  :  4

說明:

程式啟動執行main時候,java虛擬機器啟動一個程序,主執行緒main在main()呼叫時候被建立。隨著呼叫MitiSay的兩個物件的start方法,另外兩個執行緒也啟動了,這樣,整個應用就在多執行緒下執行。

 

注意:start()方法的呼叫後並不是立即執行多執行緒程式碼,而是使得該執行緒變為可執行態(Runnable),什麼時候執行是由作業系統決定的。

從程式執行的結果可以發現,多執行緒程式是亂序執行。因此,只有亂序執行的程式碼才有必要設計為多執行緒。

Thread.sleep()方法呼叫目的是不讓當前執行緒獨自霸佔該程序所獲取的CPU資源,以留出一定時間給其他執行緒執行的機會。

實際上所有的多執行緒程式碼執行順序都是不確定的,每次執行的結果都是隨機的。

但是start方法重複呼叫的話,會出現java.lang.IllegalThreadStateException異常。

 

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. Thread1 mTh1=new Thread1("A");  
  2. Thread1 mTh2=mTh1;  
  3. mTh1.start();  
  4. mTh2.start();  


輸出:

 

Exception in thread "main" java.lang.IllegalThreadStateException
    at java.lang.Thread.start(Unknown Source)
    at com.multithread.learning.Main.main(Main.java:31)

A執行  :  0
A執行  :  1
A執行  :  2
A執行  :  3
A執行  :  4

二、實現java.lang.Runnable介面

採用Runnable也是非常常見的一種,我們只需要重寫run方法即可。下面也來看個例項。

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. /** 
  2.  *@functon 多執行緒學習 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.runnable;  
  7. class Thread2 implements Runnable{  
  8.     private String name;  
  9.   
  10.     public Thread2(String name) {  
  11.         this.name=name;  
  12.     }  
  13.   
  14.     @Override  
  15.     public void run() {  
  16.           for (int i = 0; i < 5; i++) {  
  17.                 System.out.println(name + "執行  :  " + i);  
  18.                 try {  
  19.                     Thread.sleep((int) Math.random() * 10);  
  20.                 } catch (InterruptedException e) {  
  21.                     e.printStackTrace();  
  22.                 }  
  23.             }  
  24.           
  25.     }  
  26.       
  27. }  
  28. public class Main {  
  29.   
  30.     public static void main(String[] args) {  
  31.         new Thread(new Thread2("C")).start();  
  32.         new Thread(new Thread2("D")).start();  
  33.     }  
  34.   
  35. }  

輸出:

 

C執行  :  0
D執行  :  0
D執行  :  1
C執行  :  1
D執行  :  2
C執行  :  2
D執行  :  3
C執行  :  3
D執行  :  4
C執行  :  4

 

說明:

Thread2類通過實現Runnable介面,使得該類有了多執行緒類的特徵。run()方法是多執行緒程式的一個約定。所有的多執行緒程式碼都在run方法裡面。Thread類實際上也是實現了Runnable介面的類。

在啟動的多執行緒的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出物件,然後呼叫Thread物件的start()方法來執行多執行緒程式碼。

實際上所有的多執行緒程式碼都是通過執行Thread的start()方法來執行的。因此,不管是擴充套件Thread類還是實現Runnable介面來實現多執行緒,最終還是通過Thread的物件的API來控制執行緒的,熟悉Thread類的API是進行多執行緒程式設計的基礎。

三、Thread和Runnable的區別

如果一個類繼承Thread,則不適合資源共享。但是如果實現了Runable介面的話,則很容易的實現資源共享。

 

總結:

實現Runnable介面比繼承Thread類所具有的優勢:

1):適合多個相同的程式程式碼的執行緒去處理同一個資源

2):可以避免java中的單繼承的限制

3):增加程式的健壯性,程式碼可以被多個執行緒共享,程式碼和資料獨立

4):執行緒池只能放入實現Runable或callable類執行緒,不能直接放入繼承Thread的類

 

 

提醒一下大家:main方法其實也是一個執行緒。在java中所以的執行緒都是同時啟動的,至於什麼時候,哪個先執行,完全看誰先得到CPU的資源。

java中,每次程式執行至少啟動2個執行緒。一個是main執行緒,一個是垃圾收集執行緒。因為每當使用java命令執行一個類的時候,實際上都會啟動一個JVM,每一個jVM實習在就是在作業系統中啟動了一個程序。

四、執行緒狀態轉換

下面的這個圖非常重要!你如果看懂了這個圖,那麼對於多執行緒的理解將會更加深刻!

 

1、新建狀態(New):新建立了一個執行緒物件。

2、就緒狀態(Runnable):執行緒物件建立後,其他執行緒呼叫了該物件的start()方法。該狀態的執行緒位於可執行執行緒池中,變得可執行,等待獲取CPU的使用權。

3、執行狀態(Running):就緒狀態的執行緒獲取了CPU,執行程式程式碼。

4、阻塞狀態(Blocked):阻塞狀態是執行緒因為某種原因放棄CPU使用權,暫時停止執行。直到執行緒進入就緒狀態,才有機會轉到執行狀態。阻塞的情況分三種:

(一)、等待阻塞:執行的執行緒執行wait()方法,JVM會把該執行緒放入等待池中。(wait會釋放持有的鎖)

(二)、同步阻塞:執行的執行緒在獲取物件的同步鎖時,若該同步鎖被別的執行緒佔用,則JVM會把該執行緒放入鎖池中。

(三)、其他阻塞:執行的執行緒執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該執行緒置為阻塞狀態。當sleep()狀態超時、join()等待執行緒終止或者超時、或者I/O處理完畢時,執行緒重新轉入就緒狀態。(注意,sleep是不會釋放持有的鎖)

5、死亡狀態(Dead):執行緒執行完了或者因異常退出了run()方法,該執行緒結束生命週期。

五、執行緒排程

執行緒的排程

1、調整執行緒優先順序:Java執行緒有優先順序,優先順序高的執行緒會獲得較多的執行機會。

 

Java執行緒的優先順序用整數表示,取值範圍是1~10,Thread類有以下三個靜態常量:

[plain] view plain copy

  1. static int MAX_PRIORITY  
  2.           執行緒可以具有的最高優先順序,取值為10。  
  3. static int MIN_PRIORITY  
  4.           執行緒可以具有的最低優先順序,取值為1。  
  5. static int NORM_PRIORITY  
  6.           分配給執行緒的預設優先順序,取值為5。  

 

Thread類的setPriority()和getPriority()方法分別用來設定和獲取執行緒的優先順序。

 每個執行緒都有預設的優先順序。主執行緒的預設優先順序為Thread.NORM_PRIORITY。

執行緒的優先順序有繼承關係,比如A執行緒中建立了B執行緒,那麼B將和A具有相同的優先順序。

JVM提供了10個執行緒優先順序,但與常見的作業系統都不能很好的對映。如果希望程式能移植到各個作業系統中,應該僅僅使用Thread類有以下三個靜態常量作為優先順序,這樣能保證同樣的優先順序採用了同樣的排程方式。

 

2、執行緒睡眠:Thread.sleep(long millis)方法,使執行緒轉到阻塞狀態。millis引數設定睡眠的時間,以毫秒為單位。當睡眠結束後,就轉為就緒(Runnable)狀態。sleep()平臺移植性好。

 

3、執行緒等待:Object類中的wait()方法,導致當前的執行緒等待,直到其他執行緒呼叫此物件的 notify() 方法或 notifyAll() 喚醒方法。這個兩個喚醒方法也是Object類中的方法,行為等價於呼叫 wait(0) 一樣。

 

4、執行緒讓步:Thread.yield() 方法,暫停當前正在執行的執行緒物件,把執行機會讓給相同或者更高優先順序的執行緒。

 

5、執行緒加入:join()方法,等待其他執行緒終止。在當前執行緒中呼叫另一個執行緒的join()方法,則當前執行緒轉入阻塞狀態,直到另一個程序執行結束,當前執行緒再由阻塞轉為就緒狀態。

 

6、執行緒喚醒:Object類中的notify()方法,喚醒在此物件監視器上等待的單個執行緒。如果所有執行緒都在此物件上等待,則會選擇喚醒其中一個執行緒。選擇是任意性的,並在對實現做出決定時發生。執行緒通過呼叫其中一個 wait 方法,在物件的監視器上等待。 直到當前的執行緒放棄此物件上的鎖定,才能繼續執行被喚醒的執行緒。被喚醒的執行緒將以常規方式與在該物件上主動同步的其他所有執行緒進行競爭;例如,喚醒的執行緒在作為鎖定此物件的下一個執行緒方面沒有可靠的特權或劣勢。類似的方法還有一個notifyAll(),喚醒在此物件監視器上等待的所有執行緒。

 注意:Thread中suspend()和resume()兩個方法在JDK1.5中已經廢除,不再介紹。因為有死鎖傾向。

六、常用函式說明

①sleep(long millis): 在指定的毫秒數內讓當前正在執行的執行緒休眠(暫停執行)

②join():指等待t執行緒終止。

使用方式。

join是Thread類的一個方法,啟動執行緒後直接呼叫,即join()的作用是:“等待該執行緒終止”,這裡需要理解的就是該執行緒是指的主執行緒等待子執行緒的終止。也就是在子執行緒呼叫了join()方法後面的程式碼,只有等到子執行緒結束了才能執行。

 

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. Thread t = new AThread(); t.start(); t.join();  

 

為什麼要用join()方法

在很多情況下,主執行緒生成並起動了子執行緒,如果子執行緒裡要進行大量的耗時的運算,主執行緒往往將於子執行緒之前結束,但是如果主執行緒處理完其他的事務後,需要用到子執行緒的處理結果,也就是主執行緒需要等待子執行緒執行完成之後再結束,這個時候就要用到join()方法了。

不加join。

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. /** 
  2.  *@functon 多執行緒學習,join 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.join;  
  7. class Thread1 extends Thread{  
  8.     private String name;  
  9.     public Thread1(String name) {  
  10.         super(name);  
  11.        this.name=name;  
  12.     }  
  13.     public void run() {  
  14.         System.out.println(Thread.currentThread().getName() + " 執行緒執行開始!");  
  15.         for (int i = 0; i < 5; i++) {  
  16.             System.out.println("子執行緒"+name + "執行 : " + i);  
  17.             try {  
  18.                 sleep((int) Math.random() * 10);  
  19.             } catch (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             }  
  22.         }  
  23.         System.out.println(Thread.currentThread().getName() + " 執行緒執行結束!");  
  24.     }  
  25. }  
  26.   
  27. public class Main {  
  28.   
  29.     public static void main(String[] args) {  
  30.         System.out.println(Thread.currentThread().getName()+"主執行緒執行開始!");  
  31.         Thread1 mTh1=new Thread1("A");  
  32.         Thread1 mTh2=new Thread1("B");  
  33.         mTh1.start();  
  34.         mTh2.start();  
  35.         System.out.println(Thread.currentThread().getName()+ "主執行緒執行結束!");  
  36.   
  37.     }  
  38.   
  39. }  

輸出結果:
main主執行緒執行開始!
main主執行緒執行結束!
B 執行緒執行開始!
子執行緒B執行 : 0
A 執行緒執行開始!
子執行緒A執行 : 0
子執行緒B執行 : 1
子執行緒A執行 : 1
子執行緒A執行 : 2
子執行緒A執行 : 3
子執行緒A執行 : 4
A 執行緒執行結束!
子執行緒B執行 : 2
子執行緒B執行 : 3
子執行緒B執行 : 4
B 執行緒執行結束!
發現主執行緒比子執行緒早結束

加join

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. public class Main {  
  2.   
  3.     public static void main(String[] args) {  
  4.         System.out.println(Thread.currentThread().getName()+"主執行緒執行開始!");  
  5.         Thread1 mTh1=new Thread1("A");  
  6.         Thread1 mTh2=new Thread1("B");  
  7.         mTh1.start();  
  8.         mTh2.start();  
  9.         try {  
  10.             mTh1.join();  
  11.         } catch (InterruptedException e) {  
  12.             e.printStackTrace();  
  13.         }  
  14.         try {  
  15.             mTh2.join();  
  16.         } catch (InterruptedException e) {  
  17.             e.printStackTrace();  
  18.         }  
  19.         System.out.println(Thread.currentThread().getName()+ "主執行緒執行結束!");  
  20.   
  21.     }  
  22.   
  23. }  


執行結果:
main主執行緒執行開始!
A 執行緒執行開始!
子執行緒A執行 : 0
B 執行緒執行開始!
子執行緒B執行 : 0
子執行緒A執行 : 1
子執行緒B執行 : 1
子執行緒A執行 : 2
子執行緒B執行 : 2
子執行緒A執行 : 3
子執行緒B執行 : 3
子執行緒A執行 : 4
子執行緒B執行 : 4
A 執行緒執行結束!
主執行緒一定會等子執行緒都結束了才結束

③yield():暫停當前正在執行的執行緒物件,並執行其他執行緒。

        Thread.yield()方法作用是:暫停當前正在執行的執行緒物件,並執行其他執行緒。

         yield()應該做的是讓當前執行執行緒回到可執行狀態,以允許具有相同優先順序的其他執行緒獲得執行機會。因此,使用yield()的目的是讓相同優先順序的執行緒之間能適當的輪轉執行。但是,實際中無法保證yield()達到讓步目的,因為讓步的執行緒還有可能被執行緒排程程式再次選中。

 

結論:yield()從未導致執行緒轉到等待/睡眠/阻塞狀態。在大多數情況下,yield()將導致執行緒從執行狀態轉到可執行狀態,但有可能沒有效果。可看上面的圖。

[cpp] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. /** 
  2.  *@functon 多執行緒學習 yield 
  3.  *@author 林炳文 
  4.  *@time 2015.3.9 
  5.  */  
  6. package com.multithread.yield;  
  7. class ThreadYield extends Thread{  
  8.     public ThreadYield(String name) {  
  9.         super(name);  
  10.     }  
  11.    
  12.     @Override  
  13.     public void run() {  
  14.         for (int i = 1; i <= 50; i++) {  
  15.             System.out.println("" + this.getName() + "-----" + i);  
  16.             // 當i為30時,該執行緒就會把CPU時間讓掉,讓其他或者自己的執行緒執行(也就是誰先搶到誰執行)  
  17.             if (i ==30) {  
  18.                 this.yield();  
  19.             }  
  20.         }  
  21.       
  22. }  
  23. }  
  24.   
  25. public class Main {  
  26.   
  27.     public static void main(String[] args) {  
  28.           
  29.         ThreadYield yt1 = new ThreadYield("張三");  
  30.         ThreadYield yt2 = new ThreadYield("李四");  
  31.         yt1.start();  
  32.         yt2.start();  
  33.     }  
  34.   
  35. }  


執行結果:

第一種情況:李四(執行緒)當執行到30時會CPU時間讓掉,這時張三(執行緒)搶到CPU時間並執行。

第二種情況:李四(執行緒)當執行到30時會CPU時間讓掉,這時李四(執行緒)搶到CPU時間並執行。

sleep()和yield()的區別
        sleep()和yield()的區別):sleep()使當前執行緒進入停滯狀態,所以執行sleep()的執行緒在指定的時間內肯定不會被執行;yield()只是使當前執行緒重新回到可執行狀態,所以執行yield()的執行緒有可能在進入到可執行狀態後馬上又被執行。
        sleep 方法使當前執行中的執行緒睡眼一段時間,進入不可執行狀態,這段時間的長短是由程式設定的,yield 方法使當前執行緒讓出 CPU 佔有權,但讓出的時間是不可設定的。實際上,yield()方法對應瞭如下操作:先檢測當前是否有相同優先順序的執行緒處於同可執行狀態,如有,則把 CPU  的佔有權交給此執行緒,否則,繼續執行原來的執行緒。所以yield()方法稱為“退讓”,它把執行機會讓給了同等優先順序的其他執行緒
       另外,sleep 方法允許較低優先順序的執行緒獲得執行機會,但 yield()  方法執行時,當前執行緒仍處在可執行狀態,所以,不可能讓出較低優先順序的執行緒些時獲得 CPU 佔有權。在一個執行系統中,如果較高優先順序的執行緒沒有呼叫 sleep 方法,又沒有受到 I\O 阻塞,那麼,較低優先順序執行緒只能等待所有較高優先順序的執行緒執行結束,才有機會執行。 

④setPriority(): 更改執行緒的優先順序。

    MIN_PRIORITY = 1
       NORM_PRIORITY = 5
           MAX_PRIORITY = 10

用法:

Thread4 t1 = new Thread4("t1");
Thread4 t2 = new Thread4("t2");
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);


⑤interrupt():不要以為它是中斷某個執行緒!它只是線執行緒傳送一箇中斷訊號,讓執行緒在無限等待時(如死鎖時)能丟擲丟擲,從而結束執行緒,但是如果你吃掉了這個異常,那麼這個執行緒還是不會中斷的!

⑥wait()

Obj.wait(),與Obj.notify()必須要與synchronized(Obj)一起使用,也就是wait,與notify是針對已經獲取了Obj鎖進行操作,從語法角度來說就是Obj.wait(),Obj.notify必須在synchronized(Obj){...}語句塊內。從功能上來說wait就是說執行緒在獲取物件鎖後,主動釋放物件鎖,同時本執行緒休眠。直到有其它執行緒呼叫物件的notify()喚醒該執行緒,才能繼續獲取物件鎖,並繼續執行。相應的notify()就是對物件鎖的喚醒操作。但有一點需要注意的是notify()呼叫後,並不是馬上就釋放物件鎖的,而是在相應的synchronized(){}語句塊執行結束,自動釋放鎖後,JVM會在wait()物件鎖的執行緒中隨機選取一執行緒,賦予其物件鎖,喚醒執行緒,繼續執行。這樣就提供了線上程間同步、喚醒的操作。Thread.sleep()與Object.wait()二者都可以暫停當前執行緒,釋放CPU控制權,主要的區別在於Object.wait()在釋放CPU同時,釋放了物件鎖的控制。

    單單在概念上理解清楚了還不夠,需要在實際的例子中進行測試才能更好的理解。對Object.wait(),Object.notify()的應用最經典的例子,應該是三執行緒列印ABC的問題了吧,這是一道比較經典的面試題,題目要求如下:

    建立三個執行緒,A執行緒列印10次A,B執行緒列印10次B,C執行緒列印10次C,要求執行緒同時執行,交替列印10次ABC。這個問題用Object的wait(),notify()就可以很方便的解決。程式碼如下:

 

[java] view plain copy 在CODE上檢視程式碼片派生到我的程式碼片

  1. /** 
  2.  * wait用法 
  3.  * @author DreamSea  
  4.  * @time 2015.3.9  
  5.  */  
  6. package com.multithread.wait;  
  7. public class MyThreadPrinter2 implements Runnable {     
  8.         
  9.     private String name;     
  10.     pr