Java多線程--基礎篇

Java多線程系列--“基礎篇”01之 基本概念

 

多線程是Java中不可避免的一個重要主體。從本章開始,我們將展開對多線程的學習。接下來的內容,是對“JDK中新增JUC包”之前的Java多線程內容的講解,涉及到的內容包括,Object類中的wait(), notify()等接口;Thread類中的接口;synchronized關鍵字。

注:JUC包是指,Java.util.concurrent包,它是由Java大師Doug Lea完成並在JDK1.5版本添加到Java中的。


在進入後面章節的學習之前,先對了解一些多線程的相關概念。
線程狀態圖

說明
線程共包括以下5種狀態。
1. 新建狀態(New)         : 線程對象被創建後,就進入了新建狀態。例如,Thread thread = new Thread()。
2. 就緒狀態(Runnable): 也被稱爲“可執行狀態”。線程對象被創建後,其它線程調用了該對象的start()方法,從而來啓動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
3. 運行狀態(Running) : 線程獲取CPU權限進行執行。需要注意的是,線程只能從就緒狀態進入到運行狀態。
4. 阻塞狀態(Blocked)  : 阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
    (01) 等待阻塞 -- 通過調用線程的wait()方法,讓線程等待某工作的完成。
    (02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態。
    (03) 其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead)    : 線程執行完了或者因異常退出了run()方法,該線程結束生命週期。

 

這5種狀態涉及到的內容包括Object類, Thread和synchronized關鍵字。這些內容我們會在後面的章節中逐個進行學習。
Object類,定義了wait(), notify(), notifyAll()等休眠/喚醒函數。
Thread類,定義了一些列的線程操作函數。例如,sleep()休眠函數, interrupt()中斷函數, getName()獲取線程名稱等。
synchronized,是關鍵字;它區分爲synchronized代碼塊和synchronized方法。synchronized的作用是讓線程獲取對象的同步鎖。
在後面詳細介紹wait(),notify()等方法時,我們會分析爲什麼“wait(), notify()等方法要定義在Object類,而不是Thread類中”。

 

Java多線程系列--“基礎篇”02之 常用的實現多線程的兩種方式

 

概要

本章,我們學習“常用的實現多線程的2種方式”:Thread 和 Runnable
之所以說是常用的,是因爲通過還可以通過java.util.concurrent包中的線程池來實現多線程。關於線程池的內容,我們以後會詳細介紹;現在,先對的Thread和Runnable進行了解。本章內容包括:
Thread和Runnable的簡介
Thread和Runnable的異同點
Thread和Runnable的多線程的示例

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479063.html

 

Thread和Runnable簡介

Runnable 是一個接口,該接口中只包含了一個run()方法。它的定義如下:

public interface Runnable {
    public abstract void run();
}

Runnable的作用,實現多線程。我們可以定義一個類A實現Runnable接口;然後,通過new Thread(new A())等方式新建線程。

 

Thread 是一個類。Thread本身就實現了Runnable接口。它的聲明如下:

public class Thread implements Runnable {}

Thread的作用,實現多線程。

 

Thread和Runnable的異同點

Thread 和 Runnable 的相同點都是“多線程的實現方式”。
Thread 和 Runnable 的不同點
Thread 是類,而Runnable是接口;Thread本身是實現了Runnable接口的類。我們知道“一個類只能有一個父類,但是卻能實現多個接口”,因此Runnable具有更好的擴展性。
此外,Runnable還可以用於“資源的共享”。即,多個線程都是基於某一個Runnable對象建立的,它們會共享Runnable對象上的資源。
通常,建議通過“Runnable”實現多線程!

 

Thread和Runnable的多線程示例

1. Thread的多線程示例

下面通過示例更好的理解Thread和Runnable,借鑑網上一個例子比較具有說服性的例子。

複製代碼
 1 // ThreadTest.java 源碼
 2 class MyThread extends Thread{  
 3     private int ticket=10;  
 4     public void run(){
 5         for(int i=0;i<20;i++){ 
 6             if(this.ticket>0){
 7                 System.out.println(this.getName()+" 賣票:ticket"+this.ticket--);
 8             }
 9         }
10     } 
11 };
12 
13 public class ThreadTest {  
14     public static void main(String[] args) {  
15         // 啓動3個線程t1,t2,t3;每個線程各賣10張票!
16         MyThread t1=new MyThread();
17         MyThread t2=new MyThread();
18         MyThread t3=new MyThread();
19         t1.start();
20         t2.start();
21         t3.start();
22     }  
23 } 
複製代碼

運行結果

複製代碼
Thread-0 賣票:ticket10
Thread-1 賣票:ticket10
Thread-2 賣票:ticket10
Thread-1 賣票:ticket9
Thread-0 賣票:ticket9
Thread-1 賣票:ticket8
Thread-2 賣票:ticket9
Thread-1 賣票:ticket7
Thread-0 賣票:ticket8
Thread-1 賣票:ticket6
Thread-2 賣票:ticket8
Thread-1 賣票:ticket5
Thread-0 賣票:ticket7
Thread-1 賣票:ticket4
Thread-2 賣票:ticket7
Thread-1 賣票:ticket3
Thread-0 賣票:ticket6
Thread-1 賣票:ticket2
Thread-2 賣票:ticket6
Thread-2 賣票:ticket5
Thread-2 賣票:ticket4
Thread-1 賣票:ticket1
Thread-0 賣票:ticket5
Thread-2 賣票:ticket3
Thread-0 賣票:ticket4
Thread-2 賣票:ticket2
Thread-0 賣票:ticket3
Thread-2 賣票:ticket1
Thread-0 賣票:ticket2
Thread-0 賣票:ticket1
複製代碼

結果說明
(01) MyThread繼承於Thread,它是自定義個線程。每個MyThread都會賣出10張票。
(02) 主線程main創建並啓動3個MyThread子線程。每個子線程都各自賣出了10張票。

 

2. Runnable的多線程示例

下面,我們對上面的程序進行修改。通過Runnable實現一個接口,從而實現多線程。

複製代碼
 1 // RunnableTest.java 源碼
 2 class MyThread implements Runnable{  
 3     private int ticket=10;  
 4     public void run(){
 5         for(int i=0;i<20;i++){ 
 6             if(this.ticket>0){
 7                 System.out.println(Thread.currentThread().getName()+" 賣票:ticket"+this.ticket--);
 8             }
 9         }
10     } 
11 }; 
12 
13 public class RunnableTest {  
14     public static void main(String[] args) {  
15         MyThread mt=new MyThread();
16 
17         // 啓動3個線程t1,t2,t3(它們共用一個Runnable對象),這3個線程一共賣10張票!
18         Thread t1=new Thread(mt);
19         Thread t2=new Thread(mt);
20         Thread t3=new Thread(mt);
21         t1.start();
22         t2.start();
23         t3.start();
24     }  
25 }
複製代碼

運行結果

複製代碼
Thread-0 賣票:ticket10
Thread-2 賣票:ticket8
Thread-1 賣票:ticket9
Thread-2 賣票:ticket6
Thread-0 賣票:ticket7
Thread-2 賣票:ticket4
Thread-1 賣票:ticket5
Thread-2 賣票:ticket2
Thread-0 賣票:ticket3
Thread-1 賣票:ticket1
複製代碼

結果說明
(01) 和上面“MyThread繼承於Thread”不同;這裏的MyThread實現了Thread接口。
(02) 主線程main創建並啓動3個子線程,而且這3個子線程都是基於“mt這個Runnable對象”而創建的。運行結果是這3個子線程一共賣出了10張票。這說明它們是共享了MyThread接口的。

 

概要

Thread類包含start()和run()方法,它們的區別是什麼?本章將對此作出解答。本章內容包括:
start() 和 run()的區別說明
start() 和 run()的區別示例
start() 和 run()相關源碼(基於JDK1.7.0_40)

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479083.html

 

start() 和 run()的區別說明

start() : 它的作用是啓動一個新線程,新線程會執行相應的run()方法。start()不能被重複調用。
run()   : run()就和普通的成員方法一樣,可以被重複調用。單獨調用run()的話,會在當前線程中執行run(),而並不會啓動新線程!

 

下面以代碼來進行說明。

class MyThread extends Thread{  
    public void run(){
        ...
    } 
};
MyThread mythread = new MyThread();

mythread.start()會啓動一個新線程,並在新線程中運行run()方法。
而mythread.run()則會直接在當前線程中運行run()方法,並不會啓動一個新線程來運行run()。

 

start() 和 run()的區別示例

下面,通過一個簡單示例演示它們之間的區別。源碼如下:

複製代碼
 1 // Demo.java 的源碼
 2 class MyThread extends Thread{  
 3     public MyThread(String name) {
 4         super(name);
 5     }
 6 
 7     public void run(){
 8         System.out.println(Thread.currentThread().getName()+" is running");
 9     } 
10 }; 
11 
12 public class Demo {  
13     public static void main(String[] args) {  
14         Thread mythread=new MyThread("mythread");
15 
16         System.out.println(Thread.currentThread().getName()+" call mythread.run()");
17         mythread.run();
18 
19         System.out.println(Thread.currentThread().getName()+" call mythread.start()");
20         mythread.start();
21     }  
22 }
複製代碼

運行結果

main call mythread.run()
main is running
main call mythread.start()
mythread is running

結果說明
(01) Thread.currentThread().getName()是用於獲取“當前線程”的名字。當前線程是指正在cpu中調度執行的線程。
(02) mythread.run()是在“主線程main”中調用的,該run()方法直接運行在“主線程main”上。
(03) mythread.start()會啓動“線程mythread”,“線程mythread”啓動之後,會調用run()方法;此時的run()方法是運行在“線程mythread”上。

 

start() 和 run()相關源碼(基於JDK1.7.0_40)

Thread.java中start()方法的源碼如下:

複製代碼
public synchronized void start() {
    // 如果線程不是"就緒狀態",則拋出異常!
    if (threadStatus != 0)
        throw new IllegalThreadStateException();

    // 將線程添加到ThreadGroup中
    group.add(this);

    boolean started = false;
    try {
        // 通過start0()啓動線程
        start0();
        // 設置started標記
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
        }
    }
}
複製代碼

說明:start()實際上是通過本地方法start0()啓動線程的。而start0()會新運行一個線程,新線程會調用run()方法。

private native void start0();

 

Thread.java中run()的代碼如下:

public void run() {
    if (target != null) {
        target.run();
    }
}

說明:target是一個Runnable對象。run()就是直接調用Thread線程的Runnable成員的run()方法,並不會新建一個線程。

Java多線程系列--“基礎篇”04之 synchronized關鍵字

 

概要

本章,會對synchronized關鍵字進行介紹。涉及到的內容包括:
1. synchronized原理
2. synchronized基本規則
3. synchronized方法 和 synchronized代碼塊
4. 實例鎖 和 全局鎖

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479202.html

 

1. synchronized原理

在java中,每一個對象有且僅有一個同步鎖。這也意味着,同步鎖是依賴於對象而存在。
當我們調用某對象的synchronized方法時,就獲取了該對象的同步鎖。例如,synchronized(obj)就獲取了“obj這個對象”的同步鎖。
不同線程對同步鎖的訪問是互斥的。也就是說,某時間點,對象的同步鎖只能被一個線程獲取到!通過同步鎖,我們就能在多線程中,實現對“對象/方法”的互斥訪問。 例如,現在有兩個線程A和線程B,它們都會訪問“對象obj的同步鎖”。假設,在某一時刻,線程A獲取到“obj的同步鎖”並在執行一些操作;而此時,線程B也企圖獲取“obj的同步鎖” —— 線程B會獲取失敗,它必須等待,直到線程A釋放了“該對象的同步鎖”之後線程B才能獲取到“obj的同步鎖”從而纔可以運行。

 

2. synchronized基本規則

我們將synchronized的基本規則總結爲下面3條,並通過實例對它們進行說明。
第一條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的該“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
第二條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程仍然可以訪問“該對象”的非同步代碼塊
第三條: 當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。

 

第一條

當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的該“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
下面是“synchronized代碼塊”對應的演示程序。

複製代碼
 1 class MyRunable implements Runnable {
 2     
 3     @Override
 4     public void run() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 }
16 
17 public class Demo1_1 {
18 
19     public static void main(String[] args) {  
20         Runnable demo = new MyRunable();     // 新建“Runnable對象”
21 
22         Thread t1 = new Thread(demo, "t1");  // 新建“線程t1”, t1是基於demo這個Runnable對象
23         Thread t2 = new Thread(demo, "t2");  // 新建“線程t2”, t2是基於demo這個Runnable對象
24         t1.start();                          // 啓動“線程t1”
25         t2.start();                          // 啓動“線程t2” 
26     } 
27 }
複製代碼

運行結果

複製代碼
t1 loop 0
t1 loop 1
t1 loop 2
t1 loop 3
t1 loop 4
t2 loop 0
t2 loop 1
t2 loop 2
t2 loop 3
t2 loop 4
複製代碼

結果說明
run()方法中存在“synchronized(this)代碼塊”,而且t1和t2都是基於"demo這個Runnable對象"創建的線程。這就意味着,我們可以將synchronized(this)中的this看作是“demo這個Runnable對象”;因此,線程t1和t2共享“demo對象的同步鎖”。所以,當一個線程運行的時候,另外一個線程必須等待“運行線程”釋放“demo的同步鎖”之後才能運行。

如果你確認,你搞清楚這個問題了。那我們將上面的代碼進行修改,然後再運行看看結果怎麼樣,看看你是否會迷糊。修改後的源碼如下:

複製代碼
 1 class MyThread extends Thread {
 2     
 3     public MyThread(String name) {
 4         super(name);
 5     }
 6 
 7     @Override
 8     public void run() {
 9         synchronized(this) {
10             try {  
11                 for (int i = 0; i < 5; i++) {
12                     Thread.sleep(100); // 休眠100ms
13                     System.out.println(Thread.currentThread().getName() + " loop " + i);  
14                 }
15             } catch (InterruptedException ie) {  
16             }
17         }  
18     }
19 }
20 
21 public class Demo1_2 {
22 
23     public static void main(String[] args) {  
24         Thread t1 = new MyThread("t1");  // 新建“線程t1”
25         Thread t2 = new MyThread("t2");  // 新建“線程t2”
26         t1.start();                          // 啓動“線程t1”
27         t2.start();                          // 啓動“線程t2” 
28     } 
29 }
複製代碼

代碼說明
比較Demo1_2 和 Demo1_1,我們發現,Demo1_2中的MyThread類是直接繼承於Thread,而且t1和t2都是MyThread子線程。
幸運的是,在“Demo1_2的run()方法”也調用了synchronized(this),正如“Demo1_1的run()方法”也調用了synchronized(this)一樣!
那麼,Demo1_2的執行流程是不是和Demo1_1一樣呢?
運行結果:

複製代碼
t1 loop 0
t2 loop 0
t1 loop 1
t2 loop 1
t1 loop 2
t2 loop 2
t1 loop 3
t2 loop 3
t1 loop 4
t2 loop 4
複製代碼

結果說明
如果這個結果一點也不令你感到驚訝,那麼我相信你對synchronized和this的認識已經比較深刻了。否則的話,請繼續閱讀這裏的分析。
synchronized(this)中的this是指“當前的類對象”,即synchronized(this)所在的類對應的當前對象。它的作用是獲取“當前對象的同步鎖”。
對於Demo1_2中,synchronized(this)中的this代表的是MyThread對象,而t1和t2是兩個不同的MyThread對象,因此t1和t2在執行synchronized(this)時,獲取的是不同對象的同步鎖。對於Demo1_1對而言,synchronized(this)中的this代表的是MyRunable對象;t1和t2共同一個MyRunable對象,因此,一個線程獲取了對象的同步鎖,會造成另外一個線程等待。

 

第二條

當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程仍然可以訪問“該對象”的非同步代碼塊。
下面是“synchronized代碼塊”對應的演示程序。

複製代碼
 1 class Count {
 2 
 3     // 含有synchronized同步塊的方法
 4     public void synMethod() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 
16     // 非同步的方法
17     public void nonSynMethod() {
18         try {  
19             for (int i = 0; i < 5; i++) {
20                 Thread.sleep(100);
21                 System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
22             }
23         } catch (InterruptedException ie) {  
24         }
25     }
26 }
27 
28 public class Demo2 {
29 
30     public static void main(String[] args) {  
31         final Count count = new Count();
32         // 新建t1, t1會調用“count對象”的synMethod()方法
33         Thread t1 = new Thread(
34                 new Runnable() {
35                     @Override
36                     public void run() {
37                         count.synMethod();
38                     }
39                 }, "t1");
40 
41         // 新建t2, t2會調用“count對象”的nonSynMethod()方法
42         Thread t2 = new Thread(
43                 new Runnable() {
44                     @Override
45                     public void run() {
46                         count.nonSynMethod();
47                     }
48                 }, "t2");  
49 
50 
51         t1.start();  // 啓動t1
52         t2.start();  // 啓動t2
53     } 
54 }
複製代碼

運行結果

複製代碼
t1 synMethod loop 0
t2 nonSynMethod loop 0
t1 synMethod loop 1
t2 nonSynMethod loop 1
t1 synMethod loop 2
t2 nonSynMethod loop 2
t1 synMethod loop 3
t2 nonSynMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 4
複製代碼

結果說明
主線程中新建了兩個子線程t1和t2。t1會調用count對象的synMethod()方法,該方法內含有同步塊;而t2則會調用count對象的nonSynMethod()方法,該方法不是同步方法。t1運行時,雖然調用synchronized(this)獲取“count的同步鎖”;但是並沒有造成t2的阻塞,因爲t2沒有用到“count”同步鎖。

 

第三條

當一個線程訪問“某對象”的“synchronized方法”或者“synchronized代碼塊”時,其他線程對“該對象”的其他的“synchronized方法”或者“synchronized代碼塊”的訪問將被阻塞。
我們將上面的例子中的nonSynMethod()方法體的也用synchronized(this)修飾。修改後的源碼如下:

複製代碼
 1 class Count {
 2 
 3     // 含有synchronized同步塊的方法
 4     public void synMethod() {
 5         synchronized(this) {
 6             try {  
 7                 for (int i = 0; i < 5; i++) {
 8                     Thread.sleep(100); // 休眠100ms
 9                     System.out.println(Thread.currentThread().getName() + " synMethod loop " + i);  
10                 }
11             } catch (InterruptedException ie) {  
12             }
13         }  
14     }
15 
16     // 也包含synchronized同步塊的方法
17     public void nonSynMethod() {
18         synchronized(this) {
19             try {  
20                 for (int i = 0; i < 5; i++) {
21                     Thread.sleep(100);
22                     System.out.println(Thread.currentThread().getName() + " nonSynMethod loop " + i);  
23                 }
24             } catch (InterruptedException ie) {  
25             }
26         }
27     }
28 }
29 
30 public class Demo3 {
31 
32     public static void main(String[] args) {  
33         final Count count = new Count();
34         // 新建t1, t1會調用“count對象”的synMethod()方法
35         Thread t1 = new Thread(
36                 new Runnable() {
37                     @Override
38                     public void run() {
39                         count.synMethod();
40                     }
41                 }, "t1");
42 
43         // 新建t2, t2會調用“count對象”的nonSynMethod()方法
44         Thread t2 = new Thread(
45                 new Runnable() {
46                     @Override
47                     public void run() {
48                         count.nonSynMethod();
49                     }
50                 }, "t2");  
51 
52 
53         t1.start();  // 啓動t1
54         t2.start();  // 啓動t2
55     } 
56 }
複製代碼

運行結果

複製代碼
t1 synMethod loop 0
t1 synMethod loop 1
t1 synMethod loop 2
t1 synMethod loop 3
t1 synMethod loop 4
t2 nonSynMethod loop 0
t2 nonSynMethod loop 1
t2 nonSynMethod loop 2
t2 nonSynMethod loop 3
t2 nonSynMethod loop 4
複製代碼

結果說明
主線程中新建了兩個子線程t1和t2。t1和t2運行時都調用synchronized(this),這個this是Count對象(count),而t1和t2共用count。因此,在t1運行時,t2會被阻塞,等待t1運行釋放“count對象的同步鎖”,t2才能運行。

 

3. synchronized方法 和 synchronized代碼塊

synchronized方法”是用synchronized修飾方法,而 “synchronized代碼塊”則是用synchronized修飾代碼塊。

synchronized方法示例

public synchronized void foo1() {
    System.out.println("synchronized methoed");
}

synchronized代碼塊

public void foo2() {
    synchronized (this) {
        System.out.println("synchronized methoed");
    }
}

synchronized代碼塊中的this是指當前對象。也可以將this替換成其他對象,例如將this替換成obj,則foo2()在執行synchronized(obj)時就獲取的是obj的同步鎖。


synchronized代碼塊可以更精確的控制衝突限制訪問區域,有時候表現更高效率。下面通過一個示例來演示:

複製代碼
 1 // Demo4.java的源碼
 2 public class Demo4 {
 3 
 4     public synchronized void synMethod() {
 5         for(int i=0; i<1000000; i++)
 6             ;
 7     }
 8 
 9     public void synBlock() {
10         synchronized( this ) {
11             for(int i=0; i<1000000; i++)
12                 ;
13         }
14     }
15 
16     public static void main(String[] args) {
17         Demo4 demo = new Demo4();
18 
19         long start, diff;
20         start = System.currentTimeMillis();                // 獲取當前時間(millis)
21         demo.synMethod();                                // 調用“synchronized方法”
22         diff = System.currentTimeMillis() - start;        // 獲取“時間差值”
23         System.out.println("synMethod() : "+ diff);
24         
25         start = System.currentTimeMillis();                // 獲取當前時間(millis)
26         demo.synBlock();                                // 調用“synchronized方法塊”
27         diff = System.currentTimeMillis() - start;        // 獲取“時間差值”
28         System.out.println("synBlock()  : "+ diff);
29     }
30 }
複製代碼

(某一次)執行結果

synMethod() : 11
synBlock() : 3

 

4. 實例鎖 和 全局鎖

實例鎖 -- 鎖在某一個實例對象上。如果該類是單例,那麼該鎖也具有全局鎖的概念。
               實例鎖對應的就是synchronized關鍵字。
全局鎖 -- 該鎖針對的是類,無論實例多少個對象,那麼線程都共享該鎖。
               全局鎖對應的就是static synchronized(或者是鎖在該類的class或者classloader對象上)。

關於“實例鎖”和“全局鎖”有一個很形象的例子:

pulbic class Something {
    public synchronized void isSyncA(){}
    public synchronized void isSyncB(){}
    public static synchronized void cSyncA(){}
    public static synchronized void cSyncB(){}
}

假設,Something有兩個實例x和y。分析下面4組表達式獲取的鎖的情況。
(01) x.isSyncA()與x.isSyncB() 
(02) x.isSyncA()與y.isSyncA()
(03) x.cSyncA()與y.cSyncB()
(04) x.isSyncA()與Something.cSyncA()

(01) 不能被同時訪問。因爲isSyncA()和isSyncB()都是訪問同一個對象(對象x)的同步鎖!

複製代碼
 1 // LockTest1.java的源碼
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21 }
22 
23 public class LockTest1 {
24 
25     Something x = new Something();
26     Something y = new Something();
27 
28     // 比較(01) x.isSyncA()與x.isSyncB() 
29     private void test1() {
30         // 新建t11, t11會調用 x.isSyncA()
31         Thread t11 = new Thread(
32                 new Runnable() {
33                     @Override
34                     public void run() {
35                         x.isSyncA();
36                     }
37                 }, "t11");
38 
39         // 新建t12, t12會調用 x.isSyncB()
40         Thread t12 = new Thread(
41                 new Runnable() {
42                     @Override
43                     public void run() {
44                         x.isSyncB();
45                     }
46                 }, "t12");  
47 
48 
49         t11.start();  // 啓動t11
50         t12.start();  // 啓動t12
51     }
52 
53     public static void main(String[] args) {
54         LockTest1 demo = new LockTest1();
55         demo.test1();
56     }
57 }
複製代碼

運行結果

複製代碼
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t11 : isSyncA
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
t12 : isSyncB
複製代碼

 

(02) 可以同時被訪問。因爲訪問的不是同一個對象的同步鎖,x.isSyncA()訪問的是x的同步鎖,而y.isSyncA()訪問的是y的同步鎖。

複製代碼
 1 // LockTest2.java的源碼
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest2 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比較(02) x.isSyncA()與y.isSyncA()
47     private void test2() {
48         // 新建t21, t21會調用 x.isSyncA()
49         Thread t21 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.isSyncA();
54                     }
55                 }, "t21");
56 
57         // 新建t22, t22會調用 x.isSyncB()
58         Thread t22 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         y.isSyncA();
63                     }
64                 }, "t22");  
65 
66 
67         t21.start();  // 啓動t21
68         t22.start();  // 啓動t22
69     }
70 
71     public static void main(String[] args) {
72         LockTest2 demo = new LockTest2();
73 
74         demo.test2();
75     }
76 }
複製代碼

運行結果

複製代碼
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
t21 : isSyncA
t22 : isSyncA
複製代碼

 

(03) 不能被同時訪問。因爲cSyncA()和cSyncB()都是static類型,x.cSyncA()相當於Something.isSyncA(),y.cSyncB()相當於Something.isSyncB(),因此它們共用一個同步鎖,不能被同時反問。

複製代碼
 1 // LockTest3.java的源碼
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest3 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比較(03) x.cSyncA()與y.cSyncB()
47     private void test3() {
48         // 新建t31, t31會調用 x.isSyncA()
49         Thread t31 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.cSyncA();
54                     }
55                 }, "t31");
56 
57         // 新建t32, t32會調用 x.isSyncB()
58         Thread t32 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         y.cSyncB();
63                     }
64                 }, "t32");  
65 
66 
67         t31.start();  // 啓動t31
68         t32.start();  // 啓動t32
69     }
70 
71     public static void main(String[] args) {
72         LockTest3 demo = new LockTest3();
73 
74         demo.test3();
75     }
76 }
複製代碼

運行結果

複製代碼
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t31 : cSyncA
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
t32 : cSyncB
複製代碼

 

(04) 可以被同時訪問。因爲isSyncA()是實例方法,x.isSyncA()使用的是對象x的鎖;而cSyncA()是靜態方法,Something.cSyncA()可以理解對使用的是“類的鎖”。因此,它們是可以被同時訪問的。

複製代碼
 1 // LockTest4.java的源碼
 2 class Something {
 3     public synchronized void isSyncA(){
 4         try {  
 5             for (int i = 0; i < 5; i++) {
 6                 Thread.sleep(100); // 休眠100ms
 7                 System.out.println(Thread.currentThread().getName()+" : isSyncA");
 8             }
 9         }catch (InterruptedException ie) {  
10         }  
11     }
12     public synchronized void isSyncB(){
13         try {  
14             for (int i = 0; i < 5; i++) {
15                 Thread.sleep(100); // 休眠100ms
16                 System.out.println(Thread.currentThread().getName()+" : isSyncB");
17             }
18         }catch (InterruptedException ie) {  
19         }  
20     }
21     public static synchronized void cSyncA(){
22         try {  
23             for (int i = 0; i < 5; i++) {
24                 Thread.sleep(100); // 休眠100ms
25                 System.out.println(Thread.currentThread().getName()+" : cSyncA");
26             } 
27         }catch (InterruptedException ie) {  
28         }  
29     }
30     public static synchronized void cSyncB(){
31         try {  
32             for (int i = 0; i < 5; i++) {
33                 Thread.sleep(100); // 休眠100ms
34                 System.out.println(Thread.currentThread().getName()+" : cSyncB");
35             } 
36         }catch (InterruptedException ie) {  
37         }  
38     }
39 }
40 
41 public class LockTest4 {
42 
43     Something x = new Something();
44     Something y = new Something();
45 
46     // 比較(04) x.isSyncA()與Something.cSyncA()
47     private void test4() {
48         // 新建t41, t41會調用 x.isSyncA()
49         Thread t41 = new Thread(
50                 new Runnable() {
51                     @Override
52                     public void run() {
53                         x.isSyncA();
54                     }
55                 }, "t41");
56 
57         // 新建t42, t42會調用 x.isSyncB()
58         Thread t42 = new Thread(
59                 new Runnable() {
60                     @Override
61                     public void run() {
62                         Something.cSyncA();
63                     }
64                 }, "t42");  
65 
66 
67         t41.start();  // 啓動t41
68         t42.start();  // 啓動t42
69     }
70 
71     public static void main(String[] args) {
72         LockTest4 demo = new LockTest4();
73 
74         demo.test4();
75     }
76 }
複製代碼

運行結果

複製代碼
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA
t41 : isSyncA
t42 : cSyncA

Java多線程系列--“基礎篇”05之 線程等待與喚醒

 

概要

本章,會對線程等待/喚醒方法進行介紹。涉及到的內容包括:
1. wait(), notify(), notifyAll()等方法介紹
2. wait()和notify()
3. wait(long timeout)和notify()
4. wait() 和 notifyAll()
5. 爲什麼notify(), wait()等函數定義在Object中,而不是Thread中

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479224.html

 

wait(), notify(), notifyAll()等方法介紹

在Object.java中,定義了wait(), notify()和notifyAll()等接口。wait()的作用是讓當前線程進入等待狀態,同時,wait()也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用,則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有的線程。

Object類中關於等待/喚醒的API詳細信息如下:
notify()        -- 喚醒在此對象監視器上等待的單個線程。
notifyAll()   -- 喚醒在此對象監視器上等待的所有線程。
wait()                                         -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout)                    -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
wait(long timeout, int nanos)  -- 讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者其他某個線程中斷當前線程,或者已超過某個實際時間量”,當前線程被喚醒(進入“就緒狀態”)。

 

2. wait()和notify()示例

下面通過示例演示"wait()和notify()配合使用的情形"。

複製代碼
// WaitTest.java的源碼
class ThreadA extends Thread{

    public ThreadA(String name) {
        super(name);
    }

    public void run() {
        synchronized (this) {
            System.out.println(Thread.currentThread().getName()+" call notify()");
            // 喚醒當前的wait線程
            notify();
        }
    }
}

public class WaitTest {

    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");

        synchronized(t1) {
            try {
                // 啓動“線程t1”
                System.out.println(Thread.currentThread().getName()+" start t1");
                t1.start();

                // 主線程等待t1通過notify()喚醒。
                System.out.println(Thread.currentThread().getName()+" wait()");
                t1.wait();

                System.out.println(Thread.currentThread().getName()+" continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

運行結果:

main start t1
main wait()
t1 call notify()
main continue

結果說明
如下圖,說明了“主線程”和“線程t1”的流程。

(01) 注意,圖中"主線程" 代表“主線程main”。"線程t1" 代表WaitTest中啓動的“線程t1”。 而“鎖” 代表“t1這個對象的同步鎖”。
(02) “主線程”通過 new ThreadA("t1") 新建“線程t1”。隨後通過synchronized(t1)獲取“t1對象的同步鎖”。然後調用t1.start()啓動“線程t1”。
(03) “主線程”執行t1.wait() 釋放“t1對象的鎖”並且進入“等待(阻塞)狀態”。等待t1對象上的線程通過notify() 或 notifyAll()將其喚醒。
(04) “線程t1”運行之後,通過synchronized(this)獲取“當前對象的鎖”;接着調用notify()喚醒“當前對象上的等待線程”,也就是喚醒“主線程”。
(05) “線程t1”運行完畢之後,釋放“當前對象的鎖”。緊接着,“主線程”獲取“t1對象的鎖”,然後接着運行。

對於上面的代碼?曾經有個朋友問到過:t1.wait()應該是讓“線程t1”等待;但是,爲什麼卻是讓“主線程main”等待了呢?
在解答該問題前,我們先看看jdk文檔中關於wait的一段介紹:

Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object. 
In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor. The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor to wake up either through a call to the notify method or the notifyAll method. The thread then waits until it can re-obtain ownership of the monitor and resumes execution.

中文意思大概是:

引起“當前線程”等待,直到另外一個線程調用notify()或notifyAll()喚醒該線程。換句話說,這個方法和wait(0)的效果一樣!(補充,對於wait(long millis)方法,當millis爲0時,表示無限等待,直到被notify()或notifyAll()喚醒)。
“當前線程”在調用wait()時,必須擁有該對象的同步鎖。該線程調用wait()之後,會釋放該鎖;然後一直等待直到“其它線程”調用對象的同步鎖的notify()或notifyAll()方法。然後,該線程繼續等待直到它重新獲取“該對象的同步鎖”,然後就可以接着運行。

注意:jdk的解釋中,說wait()的作用是讓“當前線程”等待,而“當前線程”是指正在cpu上運行的線程!
這也意味着,雖然t1.wait()是通過“線程t1”調用的wait()方法,但是調用t1.wait()的地方是在“主線程main”中。而主線程必須是“當前線程”,也就是運行狀態,纔可以執行t1.wait()。所以,此時的“當前線程”是“主線程main”!因此,t1.wait()是讓“主線程”等待,而不是“線程t1”!

 

3. wait(long timeout)和notify()

wait(long timeout)會讓當前線程處於“等待(阻塞)狀態”,“直到其他線程調用此對象的 notify() 方法或 notifyAll() 方法,或者超過指定的時間量”,當前線程被喚醒(進入“就緒狀態”)。
下面的示例就是演示wait(long timeout)在超時情況下,線程被喚醒的情況。

複製代碼
// WaitTimeoutTest.java的源碼
class ThreadA extends Thread{

    public ThreadA(String name) {
        super(name);
    }

    public void run() {
        System.out.println(Thread.currentThread().getName() + " run ");
        // 死循環,不斷運行。
        while(true)
            ;
    }
}

public class WaitTimeoutTest {

    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");

        synchronized(t1) {
            try {
                // 啓動“線程t1”
                System.out.println(Thread.currentThread().getName() + " start t1");
                t1.start();

                // 主線程等待t1通過notify()喚醒 或 notifyAll()喚醒,或超過3000ms延時;然後才被喚醒。
                System.out.println(Thread.currentThread().getName() + " call wait ");
                t1.wait(3000);

                System.out.println(Thread.currentThread().getName() + " continue");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
複製代碼

運行結果

main start t1
main call wait 
t1 run                  // 大約3秒之後...輸出“main continue”
main continue

結果說明
如下圖,說明了“主線程”和“線程t1”的流程。
(01) 注意,圖中"主線程" 代表WaitTimeoutTest主線程(即,線程main)。"線程t1" 代表WaitTest中啓動的線程t1。 而“鎖” 代表“t1這個對象的同步鎖”。
(02) 主線程main執行t1.start()啓動“線程t1”。
(03) 主線程main執行t1.wait(3000),此時,主線程進入“阻塞狀態”。需要“用於t1對象鎖的線程通過notify() 或者 notifyAll()將其喚醒” 或者 “超時3000ms之後”,主線程main才進入到“就緒狀態”,然後纔可以運行。
(04) “線程t1”運行之後,進入了死循環,一直不斷的運行。
(05) 超時3000ms之後,主線程main會進入到“就緒狀態”,然後接着進入“運行狀態”。

 

4. wait() 和 notifyAll()

通過前面的示例,我們知道 notify() 可以喚醒在此對象監視器上等待的單個線程。
下面,我們通過示例演示notifyAll()的用法;它的作用是喚醒在此對象監視器上等待的所有線程。

複製代碼
 1 public class NotifyAllTest {
 2 
 3     private static Object obj = new Object();
 4     public static void main(String[] args) {
 5 
 6         ThreadA t1 = new ThreadA("t1");
 7         ThreadA t2 = new ThreadA("t2");
 8         ThreadA t3 = new ThreadA("t3");
 9         t1.start();
10         t2.start();
11         t3.start();
12 
13         try {
14             System.out.println(Thread.currentThread().getName()+" sleep(3000)");
15             Thread.sleep(3000);
16         } catch (InterruptedException e) {
17             e.printStackTrace();
18         }
19 
20         synchronized(obj) {
21             // 主線程等待喚醒。
22             System.out.println(Thread.currentThread().getName()+" notifyAll()");
23             obj.notifyAll();
24         }
25     }
26 
27     static class ThreadA extends Thread{
28 
29         public ThreadA(String name){
30             super(name);
31         }
32 
33         public void run() {
34             synchronized (obj) {
35                 try {
36                     // 打印輸出結果
37                     System.out.println(Thread.currentThread().getName() + " wait");
38 
39                     // 喚醒當前的wait線程
40                     obj.wait();
41 
42                     // 打印輸出結果
43                     System.out.println(Thread.currentThread().getName() + " continue");
44                 } catch (InterruptedException e) {
45                     e.printStackTrace();
46                 }
47             }
48         }
49     }
50 }
複製代碼

運行結果

複製代碼
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
複製代碼

結果說明
參考下面的流程圖。 
(01) 主線程中新建並且啓動了3個線程"t1", "t2"和"t3"。
(02) 主線程通過sleep(3000)休眠3秒。在主線程休眠3秒的過程中,我們假設"t1", "t2"和"t3"這3個線程都運行了。以"t1"爲例,當它運行的時候,它會執行obj.wait()等待其它線程通過notify()或額nofityAll()來喚醒它;相同的道理,"t2"和"t3"也會等待其它線程通過nofity()或nofityAll()來喚醒它們。
(03) 主線程休眠3秒之後,接着運行。執行 obj.notifyAll() 喚醒obj上的等待線程,即喚醒"t1", "t2"和"t3"這3個線程。 緊接着,主線程的synchronized(obj)運行完畢之後,主線程釋放“obj鎖”。這樣,"t1", "t2"和"t3"就可以獲取“obj鎖”而繼續運行了!

 

5. 爲什麼notify(), wait()等函數定義在Object中,而不是Thread中

Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。

wait()會使“當前線程”等待,因爲線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!
OK,線程調用wait()之後,會釋放它鎖持有的“同步鎖”;而且,根據前面的介紹,我們知道:等待線程可以被notify()或notifyAll()喚醒。現在,請思考一個問題:notify()是依據什麼喚醒等待線程的?或者說,wait()等待線程和notify()之間是通過什麼關聯起來的?答案是:依據“對象的同步鎖”

負責喚醒等待線程的那個線程(我們稱爲“喚醒線程”),它只有在獲取“該對象的同步鎖”(這裏的同步鎖必須和等待線程的同步鎖是同一個),並且調用notify()或notifyAll()方法之後,才能喚醒等待線程。雖然,等待線程被喚醒;但是,它不能立刻執行,因爲喚醒線程還持有“該對象的同步鎖”。必須等到喚醒線程釋放了“對象的同步鎖”之後,等待線程才能獲取到“對象的同步鎖”進而繼續運行。

總之,notify(), wait()依賴於“同步鎖”,而“同步鎖”是對象鎖持有,並且每個對象有且僅有一個!這就是爲什麼notify(), wait()等函數定義在Object類,而不是Thread類中的原因。


Java多線程系列--“基礎篇”06之 線程讓步

 

概要

本章,會對Thread中的線程讓步方法yield()進行介紹。涉及到的內容包括:
1. yield()介紹
2. yield()示例
3. yield() 與 wait()的比較

轉載請註明出處http://www.cnblogs.com/skywang12345/p/3479243.html

 

1. yield()介紹

yield()的作用是讓步。它能讓當前線程由“運行狀態”進入到“就緒狀態”,從而讓其它具有相同優先級的等待線程獲取執行權;但是,並不能保證在當前線程調用yield()之後,其它具有相同優先級的線程就一定能獲得執行權;也有可能是當前線程又進入到“運行狀態”繼續運行!

 

2. yield()示例

下面,通過示例查看它的用法。

複製代碼
 1 // YieldTest.java的源碼
 2 class ThreadA extends Thread{
 3     public ThreadA(String name){ 
 4         super(name); 
 5     } 
 6     public synchronized void run(){ 
 7         for(int i=0; i <10; i++){ 
 8             System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 
 9             // i整除4時,調用yield
10             if (i%4 == 0)
11                 Thread.yield();
12         } 
13     } 
14 } 
15 
16 public class YieldTest{ 
17     public static void main(String[] args){ 
18         ThreadA t1 = new ThreadA("t1"); 
19         ThreadA t2 = new ThreadA("t2"); 
20         t1.start(); 
21         t2.start();
22     } 
23 } 
複製代碼

(某一次的)運行結果:

複製代碼
t1 [5]:0
t2 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t1 [5]:9
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t2 [5]:9
複製代碼

結果說明
“線程t1”在能被4整數的時候,並沒有切換到“線程t2”。這表明,yield()雖然可以讓線程由“運行狀態”進入到“就緒狀態”;但是,它不一定會讓其它線程獲取CPU執行權(即,其它線程進入到“運行狀態”),即使這個“其它線程”與當前調用yield()的線程具有相同的優先級。

 

3. yield() 與 wait()的比較

我們知道,wait()的作用是讓當前線程由“運行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而yield()的作用是讓步,它也會讓當前線程離開“運行狀態”。它們的區別是:
(01) wait()是讓線程由“運行狀態”進入到“等待(阻塞)狀態”,而不yield()是讓線程由“運行狀態”進入到“就緒狀態”。
(02) wait()是會線程釋放它所持有對象的同步鎖,而yield()方法不會釋放鎖。

下面通過示例演示yield()是不會釋放鎖的。

複製代碼
 1 // YieldLockTest.java 的源碼
 2 public class YieldLockTest{ 
 3 
 4     private static Object obj = new Object();
 5 
 6     public static void main(String[] args){ 
 7         ThreadA t1 = new ThreadA("t1"); 
 8         ThreadA t2 = new ThreadA("t2"); 
 9         t1.start(); 
10         t2.start();
11     } 
12 
13     static class ThreadA extends Thread{
14         public ThreadA(String name){ 
15             super(name); 
16         } 
17         public void run(){ 
18             // 獲取obj對象的同步鎖
19             synchronized (obj) {
20                 for(int i=0; i <10; i++){ 
21                     System.out.printf("%s [%d]:%d\n", this.getName(), this.getPriority(), i); 
22                     // i整除4時,調用yield
23                     if (i%4 == 0)
24                         Thread.yield();
25                 }
26             }
27         } 
28     } 
29 } 
複製代碼

(某一次)運行結果

複製代碼
t1 [5]:0
t1 [5]:1
t1 [5]:2
t1 [5]:3
t1 [5]:4
t1 [5]:5
t1 [5]:6
t1 [5]:7
t1 [5]:8
t1 [5]:9
t2 [5]:0
t2 [5]:1
t2 [5]:2
t2 [5]:3
t2 [5]:4
t2 [5]:5
t2 [5]:6
t2 [5]:7
t2 [5]:8
t2 [5]:9
複製代碼

結果說明
主線程main中啓動了兩個線程t1和t2。t1和t2在run()會引用同一個對象的同步鎖,即synchronized(obj)。在t1運行過程中,雖然它會調用Thread.yield();但是,t2是不會獲取cpu執行權的。因爲,t1並沒有釋放“obj所持有的同步鎖”!


Java多線程系列--“基礎篇”07之 線程休眠

 

概要

本章,會對Thread中sleep()方法進行介紹。涉及到的內容包括:
1. sleep()介紹
2. sleep()示例
3. sleep() 與 wait()的比較

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479256.html

 

1. sleep()介紹

sleep() 定義在Thread.java中。
sleep() 的作用是讓當前線程休眠,即當前線程會從“運行狀態”進入到“休眠(阻塞)狀態”。sleep()會指定休眠時間,線程休眠的時間會大於/等於該休眠時間;在線程重新被喚醒時,它會由“阻塞狀態”變成“就緒狀態”,從而等待cpu的調度執行。

 

2. sleep()示例

下面通過一個簡單示例演示sleep()的用法。

複製代碼
 1 // SleepTest.java的源碼
 2 class ThreadA extends Thread{
 3     public ThreadA(String name){ 
 4         super(name); 
 5     } 
 6     public synchronized void run() { 
 7         try {
 8             for(int i=0; i <10; i++){ 
 9                 System.out.printf("%s: %d\n", this.getName(), i); 
10                 // i能被4整除時,休眠100毫秒
11                 if (i%4 == 0)
12                     Thread.sleep(100);
13             } 
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17     } 
18 } 
19 
20 public class SleepTest{ 
21     public static void main(String[] args){ 
22         ThreadA t1 = new ThreadA("t1"); 
23         t1.start(); 
24     } 
25 } 
複製代碼

運行結果

複製代碼
t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
複製代碼

結果說明
程序比較簡單,在主線程main中啓動線程t1。t1啓動之後,當t1中的計算i能被4整除時,t1會通過Thread.sleep(100)休眠100毫秒。

 

3. sleep() 與 wait()的比較

我們知道,wait()的作用是讓當前線程由“運行狀態”進入“等待(阻塞)狀態”的同時,也會釋放同步鎖。而sleep()的作用是也是讓當前線程由“運行狀態”進入到“休眠(阻塞)狀態”。
但是,wait()會釋放對象的同步鎖,而sleep()則不會釋放鎖。
下面通過示例演示sleep()是不會釋放鎖的。

複製代碼
 1 // SleepLockTest.java的源碼
 2 public class SleepLockTest{ 
 3 
 4     private static Object obj = new Object();
 5 
 6     public static void main(String[] args){ 
 7         ThreadA t1 = new ThreadA("t1"); 
 8         ThreadA t2 = new ThreadA("t2"); 
 9         t1.start(); 
10         t2.start();
11     } 
12 
13     static class ThreadA extends Thread{
14         public ThreadA(String name){ 
15             super(name); 
16         } 
17         public void run(){ 
18             // 獲取obj對象的同步鎖
19             synchronized (obj) {
20                 try {
21                     for(int i=0; i <10; i++){ 
22                         System.out.printf("%s: %d\n", this.getName(), i); 
23                         // i能被4整除時,休眠100毫秒
24                         if (i%4 == 0)
25                             Thread.sleep(100);
26                     }
27                 } catch (InterruptedException e) {
28                     e.printStackTrace();
29                 }
30             }
31         } 
32     } 
33 } 
複製代碼

運行結果

複製代碼
t1: 0
t1: 1
t1: 2
t1: 3
t1: 4
t1: 5
t1: 6
t1: 7
t1: 8
t1: 9
t2: 0
t2: 1
t2: 2
t2: 3
t2: 4
t2: 5
t2: 6
t2: 7
t2: 8
t2: 9
複製代碼

結果說明
主線程main中啓動了兩個線程t1和t2。t1和t2在run()會引用同一個對象的同步鎖,即synchronized(obj)。在t1運行過程中,雖然它會調用Thread.sleep(100);但是,t2是不會獲取cpu執行權的。因爲,t1並沒有釋放“obj所持有的同步鎖”!
注意,若我們註釋掉synchronized (obj)後再次執行該程序,t1和t2是可以相互切換的。下面是註釋調synchronized(obj) 之後的源碼:

複製代碼
 1 // SleepLockTest.java的源碼(註釋掉synchronized(obj))
 2 public class SleepLockTest{ 
 3 
 4     private static Object obj = new Object();
 5 
 6     public static void main(String[] args){ 
 7         ThreadA t1 = new ThreadA("t1"); 
 8         ThreadA t2 = new ThreadA("t2"); 
 9         t1.start(); 
10         t2.start();
11     } 
12 
13     static class ThreadA extends Thread{
14         public ThreadA(String name){ 
15             super(name); 
16         } 
17         public void run(){ 
18             // 獲取obj對象的同步鎖
19 //            synchronized (obj) {
20                 try {
21                     for(int i=0; i <10; i++){ 
22                         System.out.printf("%s: %d\n", this.getName(), i); 
23                         // i能被4整除時,休眠100毫秒
24                         if (i%4 == 0)
25                             Thread.sleep(100);
26                     }
27                 } catch (InterruptedException e) {
28                     e.printStackTrace();
29                 }
30 //            }
31         } 
32     } 
33 } 

Java多線程系列--“基礎篇”08之 join()

 

概要

本章,會對Thread中join()方法進行介紹。涉及到的內容包括:
1. join()介紹
2. join()源碼分析(基於JDK1.7.0_40)
3. join()示例

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479275.html

 

1. join()介紹

join() 定義在Thread.java中。
join() 的作用:讓“主線程”等待“子線程”結束之後才能繼續運行。這句話可能有點晦澀,我們還是通過例子去理解:

複製代碼
// 主線程
public class Father extends Thread {
    public void run() {
        Son s = new Son();
        s.start();
        s.join();
        ...
    }
}
// 子線程
public class Son extends Thread {
    public void run() {
        ...
    }
}
複製代碼

說明
上面的有兩個類Father(主線程類)和Son(子線程類)。因爲Son是在Father中創建並啓動的,所以,Father是主線程類,Son是子線程類。
在Father主線程中,通過new Son()新建“子線程s”。接着通過s.start()啓動“子線程s”,並且調用s.join()。在調用s.join()之後,Father主線程會一直等待,直到“子線程s”運行完畢;在“子線程s”運行完畢之後,Father主線程才能接着運行。 這也就是我們所說的“join()的作用,是讓主線程會等待子線程結束之後才能繼續運行”!

 

2. join()源碼分析(基於JDK1.7.0_40)

複製代碼
public final void join() throws InterruptedException {
    join(0);
}

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}
複製代碼

說明
從代碼中,我們可以發現。當millis==0時,會進入while(isAlive())循環;即只要子線程是活的,主線程就不停的等待。
我們根據上面解釋join()作用時的代碼來理解join()的用法!
問題
雖然s.join()被調用的地方是發生在“Father主線程”中,但是s.join()是通過“子線程s”去調用的join()。那麼,join()方法中的isAlive()應該是判斷“子線程s”是不是Alive狀態;對應的wait(0)也應該是“讓子線程s”等待纔對。但如果是這樣的話,s.join()的作用怎麼可能是“讓主線程等待,直到子線程s完成爲止”呢,應該是讓"子線程等待纔對(因爲調用子線程對象s的wait方法嘛)"?
答案wait()的作用是讓“當前線程”等待,而這裏的“當前線程”是指當前在CPU上運行的線程。所以,雖然是調用子線程的wait()方法,但是它是通過“主線程”去調用的;所以,休眠的是主線程,而不是“子線程”!

 

3. join()示例

在理解join()的作用之後,接下來通過示例查看join()的用法。

複製代碼
// JoinTest.java的源碼
public class JoinTest{ 

    public static void main(String[] args){ 
        try {
            ThreadA t1 = new ThreadA("t1"); // 新建“線程t1”

            t1.start();                     // 啓動“線程t1”
            t1.join();                        // 將“線程t1”加入到“主線程main”中,並且“主線程main()會等待它的完成”
            System.out.printf("%s finish\n", Thread.currentThread().getName()); 
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    } 

    static class ThreadA extends Thread{

        public ThreadA(String name){ 
            super(name); 
        } 
        public void run(){ 
            System.out.printf("%s start\n", this.getName()); 

            // 延時操作
            for(int i=0; i <1000000; i++)
               ;

            System.out.printf("%s finish\n", this.getName()); 
        } 
    } 
}
複製代碼

運行結果

t1 start
t1 finish
main finish

結果說明
運行流程如圖 
(01) 在“主線程main”中通過 new ThreadA("t1") 新建“線程t1”。 接着,通過 t1.start() 啓動“線程t1”,並執行t1.join()。
(02) 執行t1.join()之後,“主線程main”會進入“阻塞狀態”等待t1運行結束。“子線程t1”結束之後,會喚醒“主線程main”,“主線程”重新獲取cpu執行權,繼續運行。


Java多線程系列--“基礎篇”09之 interrupt()和線程終止方式

 

概要

本章,會對線程的interrupt()中斷和終止方式進行介紹。涉及到的內容包括:
1. interrupt()說明
2. 終止線程的方式
  2.1 終止處於“阻塞狀態”的線程
  2.2 終止處於“運行狀態”的線程
3. 終止線程的示例
4. interrupted() 和 isInterrupted()的區別

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479949.html

 

1. interrupt()說明

在介紹終止線程的方式之前,有必要先對interrupt()進行了解。
關於interrupt(),java的djk文檔描述如下:http://docs.oracle.com/javase/7/docs/api/

複製代碼
Interrupts this thread.
Unless the current thread is interrupting itself, which is always permitted, the checkAccess method of this thread is invoked, which may cause a SecurityException to be thrown.

If this thread is blocked in an invocation of the wait(), wait(long), or wait(long, int) methods of the Object class, or of the join(), join(long), join(long, int), sleep(long), or sleep(long, int), methods of this class, then its interrupt status will be cleared and it will receive an InterruptedException.

If this thread is blocked in an I/O operation upon an interruptible channel then the channel will be closed, the thread's interrupt status will be set, and the thread will receive a ClosedByInterruptException.

If this thread is blocked in a Selector then the thread's interrupt status will be set and it will return immediately from the selection operation, possibly with a non-zero value, just as if the selector's wakeup method were invoked.

If none of the previous conditions hold then this thread's interrupt status will be set.

Interrupting a thread that is not alive need not have any effect.
複製代碼

大致意思是:

複製代碼
interrupt()的作用是中斷本線程。
本線程中斷自己是被允許的;其它線程調用本線程的interrupt()方法時,會通過checkAccess()檢查權限。這有可能拋出SecurityException異常。
如果本線程是處於阻塞狀態:調用線程的wait(), wait(long)或wait(long, int)會讓它進入等待(阻塞)狀態,或者調用線程的join(), join(long), join(long, int), sleep(long), sleep(long, int)也會讓它進入阻塞狀態。若線程在阻塞狀態時,調用了它的interrupt()方法,那麼它的“中斷狀態”會被清除並且會收到一個InterruptedException異常。例如,線程通過wait()進入阻塞狀態,此時通過interrupt()中斷該線程;調用interrupt()會立即將線程的中斷標記設爲“true”,但是由於線程處於阻塞狀態,所以該“中斷標記”會立即被清除爲“false”,同時,會產生一個InterruptedException的異常。
如果線程被阻塞在一個Selector選擇器中,那麼通過interrupt()中斷它時;線程的中斷標記會被設置爲true,並且它會立即從選擇操作中返回。
如果不屬於前面所說的情況,那麼通過interrupt()中斷線程時,它的中斷標記會被設置爲“true”。
中斷一個“已終止的線程”不會產生任何操作。
複製代碼

 

2. 終止線程的方式

Thread中的stop()和suspend()方法,由於固有的不安全性,已經建議不再使用!
下面,我先分別討論線程在“阻塞狀態”和“運行狀態”的終止方式,然後再總結出一個通用的方式。

2.1 終止處於“阻塞狀態”的線程

通常,我們通過“中斷”方式終止處於“阻塞狀態”的線程。
當線程由於被調用了sleep(), wait(), join()等方法而進入阻塞狀態;若此時調用線程的interrupt()將線程的中斷標記設爲true。由於處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException異常。將InterruptedException放在適當的爲止就能終止線程,形式如下:

複製代碼
@Override
public void run() {
    try {
        while (true) {
            // 執行任務...
        }
    } catch (InterruptedException ie) {  
        // 由於產生InterruptedException異常,退出while(true)循環,線程終止!
    }
}
複製代碼

說明:在while(true)中不斷的執行任務,當線程處於阻塞狀態時,調用線程的interrupt()產生InterruptedException中斷。中斷的捕獲在while(true)之外,這樣就退出了while(true)循環!
注意:對InterruptedException的捕獲務一般放在while(true)循環體的外面,這樣,在產生異常時就退出了while(true)循環。否則,InterruptedException在while(true)循環體之內,就需要額外的添加退出處理。形式如下:

複製代碼
@Override
public void run() {
    while (true) {
        try {
            // 執行任務...
        } catch (InterruptedException ie) {  
            // InterruptedException在while(true)循環體內。
            // 當線程產生了InterruptedException異常時,while(true)仍能繼續運行!需要手動退出
            break;
        }
    }
}
複製代碼

說明:上面的InterruptedException異常的捕獲在whle(true)之內。當產生InterruptedException異常時,被catch處理之外,仍然在while(true)循環體內;要退出while(true)循環體,需要額外的執行退出while(true)的操作。

2.2 終止處於“運行狀態”的線程

通常,我們通過“標記”方式終止處於“運行狀態”的線程。其中,包括“中斷標記”和“額外添加標記”。
(01) 通過“中斷標記”終止線程。
形式如下:

@Override
public void run() {
    while (!isInterrupted()) {
        // 執行任務...
    }
}

說明:isInterrupted()是判斷線程的中斷標記是不是爲true。當線程處於運行狀態,並且我們需要終止它時;可以調用線程的interrupt()方法,使用線程的中斷標記爲true,即isInterrupted()會返回true。此時,就會退出while循環。
注意:interrupt()並不會終止處於“運行狀態”的線程!它會將線程的中斷標記設爲true。

(02) 通過“額外添加標記”。
形式如下:

複製代碼
private volatile boolean flag= true;
protected void stopTask() {
    flag = false;
}

@Override
public void run() {
    while (flag) {
        // 執行任務...
    }
}
複製代碼

說明:線程中有一個flag標記,它的默認值是true;並且我們提供stopTask()來設置flag標記。當我們需要終止該線程時,調用該線程的stopTask()方法就可以讓線程退出while循環。
注意:將flag定義爲volatile類型,是爲了保證flag的可見性。即其它線程通過stopTask()修改了flag之後,本線程能看到修改後的flag的值。

 

綜合線程處於“阻塞狀態”和“運行狀態”的終止方式,比較通用的終止線程的形式如下:

複製代碼
@Override
public void run() {
    try {
        // 1. isInterrupted()保證,只要中斷標記爲true就終止線程。
        while (!isInterrupted()) {
            // 執行任務...
        }
    } catch (InterruptedException ie) {  
        // 2. InterruptedException異常保證,當InterruptedException異常產生時,線程被終止。
    }
}
複製代碼

 

3. 終止線程的示例

interrupt()常常被用來終止“阻塞狀態”線程。參考下面示例:

複製代碼
 1 // Demo1.java的源碼
 2 class MyThread extends Thread {
 3     
 4     public MyThread(String name) {
 5         super(name);
 6     }
 7 
 8     @Override
 9     public void run() {
10         try {  
11             int i=0;
12             while (!isInterrupted()) {
13                 Thread.sleep(100); // 休眠100ms
14                 i++;
15                 System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
16             }
17         } catch (InterruptedException e) {  
18             System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
19         }
20     }
21 }
22 
23 public class Demo1 {
24 
25     public static void main(String[] args) {  
26         try {  
27             Thread t1 = new MyThread("t1");  // 新建“線程t1”
28             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
29 
30             t1.start();                      // 啓動“線程t1”
31             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
32 
33             // 主線程休眠300ms,然後主線程給t1發“中斷”指令。
34             Thread.sleep(300);
35             t1.interrupt();
36             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
37 
38             // 主線程休眠300ms,然後查看t1的狀態。
39             Thread.sleep(300);
40             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
41         } catch (InterruptedException e) {  
42             e.printStackTrace();
43         }
44     } 
45 }
複製代碼

運行結果

複製代碼
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (TERMINATED) is interrupted now.
複製代碼

結果說明
(01) 主線程main中通過new MyThread("t1")創建線程t1,之後通過t1.start()啓動線程t1。
(02) t1啓動之後,會不斷的檢查它的中斷標記,如果中斷標記爲“false”;則休眠100ms。
(03) t1休眠之後,會切換到主線程main;主線程再次運行時,會執行t1.interrupt()中斷線程t1。t1收到中斷指令之後,會將t1的中斷標記設置“false”,而且會拋出InterruptedException異常。在t1的run()方法中,是在循環體while之外捕獲的異常;因此循環被終止。

我們對上面的結果進行小小的修改,將run()方法中捕獲InterruptedException異常的代碼塊移到while循環體內。

複製代碼
 1 // Demo2.java的源碼
 2 class MyThread extends Thread {
 3     
 4     public MyThread(String name) {
 5         super(name);
 6     }
 7 
 8     @Override
 9     public void run() {
10         int i=0;
11         while (!isInterrupted()) {
12             try {
13                 Thread.sleep(100); // 休眠100ms
14             } catch (InterruptedException ie) {  
15                 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
16             }
17             i++;
18             System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
19         }
20     }
21 }
22 
23 public class Demo2 {
24 
25     public static void main(String[] args) {  
26         try {  
27             Thread t1 = new MyThread("t1");  // 新建“線程t1”
28             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
29 
30             t1.start();                      // 啓動“線程t1”
31             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
32 
33             // 主線程休眠300ms,然後主線程給t1發“中斷”指令。
34             Thread.sleep(300);
35             t1.interrupt();
36             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
37 
38             // 主線程休眠300ms,然後查看t1的狀態。
39             Thread.sleep(300);
40             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
41         } catch (InterruptedException e) {  
42             e.printStackTrace();
43         }
44     } 
45 }
複製代碼

運行結果

複製代碼
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) catch InterruptedException.
t1 (RUNNABLE) loop 3
t1 (RUNNABLE) loop 4
t1 (RUNNABLE) loop 5
t1 (TIMED_WAITING) is interrupted now.
t1 (RUNNABLE) loop 6
t1 (RUNNABLE) loop 7
t1 (RUNNABLE) loop 8
t1 (RUNNABLE) loop 9
...
複製代碼

結果說明
程序進入了死循環!
爲什麼會這樣呢?這是因爲,t1在“等待(阻塞)狀態”時,被interrupt()中斷;此時,會清除中斷標記[即isInterrupted()會返回false],而且會拋出InterruptedException異常[該異常在while循環體內被捕獲]。因此,t1理所當然的會進入死循環了。
解決該問題,需要我們在捕獲異常時,額外的進行退出while循環的處理。例如,在MyThread的catch(InterruptedException)中添加break 或 return就能解決該問題。

下面是通過“額外添加標記”的方式終止“狀態狀態”的線程的示例:

複製代碼
 1 // Demo3.java的源碼
 2 class MyThread extends Thread {
 3 
 4     private volatile boolean flag= true;
 5     public void stopTask() {
 6         flag = false;
 7     }
 8     
 9     public MyThread(String name) {
10         super(name);
11     }
12 
13     @Override
14     public void run() {
15         synchronized(this) {
16             try {
17                 int i=0;
18                 while (flag) {
19                     Thread.sleep(100); // 休眠100ms
20                     i++;
21                     System.out.println(Thread.currentThread().getName()+" ("+this.getState()+") loop " + i);  
22                 }
23             } catch (InterruptedException ie) {  
24                 System.out.println(Thread.currentThread().getName() +" ("+this.getState()+") catch InterruptedException.");  
25             }
26         }  
27     }
28 }
29 
30 public class Demo3 {
31 
32     public static void main(String[] args) {  
33         try {  
34             MyThread t1 = new MyThread("t1");  // 新建“線程t1”
35             System.out.println(t1.getName() +" ("+t1.getState()+") is new.");  
36 
37             t1.start();                      // 啓動“線程t1”
38             System.out.println(t1.getName() +" ("+t1.getState()+") is started.");  
39 
40             // 主線程休眠300ms,然後主線程給t1發“中斷”指令。
41             Thread.sleep(300);
42             t1.stopTask();
43             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted.");
44 
45             // 主線程休眠300ms,然後查看t1的狀態。
46             Thread.sleep(300);
47             System.out.println(t1.getName() +" ("+t1.getState()+") is interrupted now.");
48         } catch (InterruptedException e) {  
49             e.printStackTrace();
50         }
51     } 
52 }
複製代碼

運行結果

複製代碼
t1 (NEW) is new.
t1 (RUNNABLE) is started.
t1 (RUNNABLE) loop 1
t1 (RUNNABLE) loop 2
t1 (TIMED_WAITING) is interrupted.
t1 (RUNNABLE) loop 3
t1 (TERMINATED) is interrupted now.
複製代碼

 

4. interrupted() 和 isInterrupted()的區別

最後談談 interrupted() 和 isInterrupted()。
interrupted() 和 isInterrupted()都能夠用於檢測對象的“中斷標記”。
區別是,interrupted()除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設爲false);而isInterrupted()僅僅返回中斷標記。


Java多線程系列--“基礎篇”10之 線程優先級和守護線程

 

概要

本章,會對守護線程和線程優先級進行介紹。涉及到的內容包括:
1. 線程優先級的介紹
2. 線程優先級的示例
3. 守護線程的示例

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3479982.html

 

1. 線程優先級的介紹

java 中的線程優先級的範圍是1~10,默認的優先級是5。“高優先級線程”會優先於“低優先級線程”執行。

java 中有兩種線程:用戶線程守護線程。可以通過isDaemon()方法來區別它們:如果返回false,則說明該線程是“用戶線程”;否則就是“守護線程”。
用戶線程一般用戶執行用戶級任務,而守護線程也就是“後臺線程”,一般用來執行後臺任務。需要注意的是:Java虛擬機在“用戶線程”都結束後會後退出。

JDK 中關於線程優先級和守護線程的介紹如下:

複製代碼
Every thread has a priority. Threads with higher priority are executed in preference to threads with lower priority. Each thread may or may not also be marked as a daemon. When code running in some thread creates a new Thread object, the new thread has its priority initially set equal to the priority of the creating thread, and is a daemon thread if and only if the creating thread is a daemon.

When a Java Virtual Machine starts up, there is usually a single non-daemon thread (which typically calls the method named main of some designated class). The Java Virtual Machine continues to execute threads until either of the following occurs:

The exit method of class Runtime has been called and the security manager has permitted the exit operation to take place.
All threads that are not daemon threads have died, either by returning from the call to the run method or by throwing an exception that propagates beyond the run method. 
Marks this thread as either a daemon thread or a user thread. The Java Virtual Machine exits when the only threads running are all daemon threads.
複製代碼

大致意思是:

複製代碼
每個線程都有一個優先級。“高優先級線程”會優先於“低優先級線程”執行。每個線程都可以被標記爲一個守護進程或非守護進程。在一些運行的主線程中創建新的子線程時,子線程的優先級被設置爲等於“創建它的主線程的優先級”,當且僅當“創建它的主線程是守護線程”時“子線程纔會是守護線程”。

當Java虛擬機啓動時,通常有一個單一的非守護線程(該線程通過是通過main()方法啓動)。JVM會一直運行直到下面的任意一個條件發生,JVM就會終止運行:
(01) 調用了exit()方法,並且exit()有權限被正常執行。
(02) 所有的“非守護線程”都死了(即JVM中僅僅只有“守護線程”)。

每一個線程都被標記爲“守護線程”或“用戶線程”。當只有守護線程運行時,JVM會自動退出。
複製代碼

 

2. 線程優先級的示例

我們先看看優先級的示例 

複製代碼
 1 class MyThread extends Thread{  
 2     public MyThread(String name) {
 3         super(name);
 4     }
 5 
 6     public void run(){
 7         for (int i=0; i<5; i++) {
 8             System.out.println(Thread.currentThread().getName()
 9                     +"("+Thread.currentThread().getPriority()+ ")"
10                     +", loop "+i);
11         }
12     } 
13 }; 
14 
15 public class Demo {  
16     public static void main(String[] args) {  
17 
18         System.out.println(Thread.currentThread().getName()
19                 +"("+Thread.currentThread().getPriority()+ ")");
20 
21         Thread t1=new MyThread("t1");    // 新建t1
22         Thread t2=new MyThread("t2");    // 新建t2
23         t1.setPriority(1);                // 設置t1的優先級爲1
24         t2.setPriority(10);                // 設置t2的優先級爲10
25         t1.start();                        // 啓動t1
26         t2.start();                        // 啓動t2
27     }  
28 }
複製代碼

運行結果

複製代碼
main(5)
t1(1), loop 0
t2(10), loop 0
t1(1), loop 1
t2(10), loop 1
t1(1), loop 2
t2(10), loop 2
t1(1), loop 3
t2(10), loop 3
t1(1), loop 4
t2(10), loop 4
複製代碼

結果說明
(01) 主線程main的優先級是5。
(02) t1的優先級被設爲1,而t2的優先級被設爲10。cpu在執行t1和t2的時候,根據時間片輪循調度,所以能夠併發執行。

 

3. 守護線程的示例

下面是守護線程的示例。

複製代碼
 1 // Demo.java
 2 class MyThread extends Thread{  
 3     public MyThread(String name) {
 4         super(name);
 5     }
 6 
 7     public void run(){
 8         try {
 9             for (int i=0; i<5; i++) {
10                 Thread.sleep(3);
11                 System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
12             }
13         } catch (InterruptedException e) {
14         }
15     } 
16 }; 
17 
18 class MyDaemon extends Thread{  
19     public MyDaemon(String name) {
20         super(name);
21     }
22 
23     public void run(){
24         try {
25             for (int i=0; i<10000; i++) {
26                 Thread.sleep(1);
27                 System.out.println(this.getName() +"(isDaemon="+this.isDaemon()+ ")" +", loop "+i);
28             }
29         } catch (InterruptedException e) {
30         }
31     } 
32 }
33 public class Demo {  
34     public static void main(String[] args) {  
35 
36         System.out.println(Thread.currentThread().getName()
37                 +"(isDaemon="+Thread.currentThread().isDaemon()+ ")");
38 
39         Thread t1=new MyThread("t1");    // 新建t1
40         Thread t2=new MyDaemon("t2");    // 新建t2
41         t2.setDaemon(true);                // 設置t2爲守護線程
42         t1.start();                        // 啓動t1
43         t2.start();                        // 啓動t2
44     }  
45 }
複製代碼

運行結果

複製代碼
main(isDaemon=false)
t2(isDaemon=true), loop 0
t2(isDaemon=true), loop 1
t1(isDaemon=false), loop 0
t2(isDaemon=true), loop 2
t2(isDaemon=true), loop 3
t1(isDaemon=false), loop 1
t2(isDaemon=true), loop 4
t2(isDaemon=true), loop 5
t2(isDaemon=true), loop 6
t1(isDaemon=false), loop 2
t2(isDaemon=true), loop 7
t2(isDaemon=true), loop 8
t2(isDaemon=true), loop 9
t1(isDaemon=false), loop 3
t2(isDaemon=true), loop 10
t2(isDaemon=true), loop 11
t1(isDaemon=false), loop 4
t2(isDaemon=true), loop 12
複製代碼

結果說明
(01) 主線程main是用戶線程,它創建的子線程t1也是用戶線程。
(02) t2是守護線程。在“主線程main”和“子線程t1”(它們都是用戶線程)執行完畢,只剩t2這個守護線程的時候,JVM自動退出。


Java多線程系列--“基礎篇”11之 生產消費者問題

 

概要

本章,會對“生產/消費者問題”進行討論。涉及到的內容包括:
1. 生產/消費者模型
2. 生產/消費者實現

轉載請註明出處:http://www.cnblogs.com/skywang12345/p/3480016.html

 

1. 生產/消費者模型

生產/消費者問題是個非常典型的多線程問題,涉及到的對象包括“生產者”、“消費者”、“倉庫”和“產品”。他們之間的關係如下:
(01) 生產者僅僅在倉儲未滿時候生產,倉滿則停止生產。
(02) 消費者僅僅在倉儲有產品時候才能消費,倉空則等待。
(03) 當消費者發現倉儲沒產品可消費時候會通知生產者生產。
(04) 生產者在生產出可消費產品時候,應該通知等待的消費者去消費。

 

2. 生產/消費者實現

下面通過wait()/notify()方式實現該模型(後面在學習了線程池相關內容之後,再通過其它方式實現生產/消費者模型)。源碼如下:

複製代碼
  1 // Demo1.java
  2 // 倉庫
  3 class Depot {
  4     private int capacity;    // 倉庫的容量
  5     private int size;        // 倉庫的實際數量
  6 
  7     public Depot(int capacity) {
  8         this.capacity = capacity;
  9         this.size = 0;
 10     }
 11 
 12     public synchronized void produce(int val) {
 13         try {
 14              // left 表示“想要生產的數量”(有可能生產量太多,需多此生產)
 15             int left = val;
 16             while (left > 0) {
 17                 // 庫存已滿時,等待“消費者”消費產品。
 18                 while (size >= capacity)
 19                     wait();
 20                 // 獲取“實際生產的數量”(即庫存中新增的數量)
 21                 // 如果“庫存”+“想要生產的數量”>“總的容量”,則“實際增量”=“總的容量”-“當前容量”。(此時填滿倉庫)
 22                 // 否則“實際增量”=“想要生產的數量”
 23                 int inc = (size+left)>capacity ? (capacity-size) : left;
 24                 size += inc;
 25                 left -= inc;
 26                 System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n", 
 27                         Thread.currentThread().getName(), val, left, inc, size);
 28                 // 通知“消費者”可以消費了。
 29                 notifyAll();
 30             }
 31         } catch (InterruptedException e) {
 32         }
 33     } 
 34 
 35     public synchronized void consume(int val) {
 36         try {
 37             // left 表示“客戶要消費數量”(有可能消費量太大,庫存不夠,需多此消費)
 38             int left = val;
 39             while (left > 0) {
 40                 // 庫存爲0時,等待“生產者”生產產品。
 41                 while (size <= 0)
 42                     wait();
 43                 // 獲取“實際消費的數量”(即庫存中實際減少的數量)
 44                 // 如果“庫存”<“客戶要消費的數量”,則“實際消費量”=“庫存”;
 45                 // 否則,“實際消費量”=“客戶要消費的數量”。
 46                 int dec = (size<left) ? size : left;
 47                 size -= dec;
 48                 left -= dec;
 49                 System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n", 
 50                         Thread.currentThread().getName(), val, left, dec, size);
 51                 notifyAll();
 52             }
 53         } catch (InterruptedException e) {
 54         }
 55     }
 56 
 57     public String toString() {
 58         return "capacity:"+capacity+", actual size:"+size;
 59     }
 60 } 
 61 
 62 // 生產者
 63 class Producer {
 64     private Depot depot;
 65     
 66     public Producer(Depot depot) {
 67         this.depot = depot;
 68     }
 69 
 70     // 消費產品:新建一個線程向倉庫中生產產品。
 71     public void produce(final int val) {
 72         new Thread() {
 73             public void run() {
 74                 depot.produce(val);
 75             }
 76         }.start();
 77     }
 78 }
 79 
 80 // 消費者
 81 class Customer {
 82     private Depot depot;
 83     
 84     public Customer(Depot depot) {
 85         this.depot = depot;
 86     }
 87 
 88     // 消費產品:新建一個線程從倉庫中消費產品。
 89     public void consume(final int val) {
 90         new Thread() {
 91             public void run() {
 92                 depot.consume(val);
 93             }
 94         }.start();
 95     }
 96 }
 97 
 98 public class Demo1 {  
 99     public static void main(String[] args) {  
100         Depot mDepot = new Depot(100);
101         Producer mPro = new Producer(mDepot);
102         Customer mCus = new Customer(mDepot);
103 
104         mPro.produce(60);
105         mPro.produce(120);
106         mCus.consume(90);
107         mCus.consume(150);
108         mPro.produce(110);
109     }
110 }
複製代碼

說明
(01) Producer是“生產者”類,它與“倉庫(depot)”關聯。當調用“生產者”的produce()方法時,它會新建一個線程並向“倉庫”中生產產品。
(02) Customer是“消費者”類,它與“倉庫(depot)”關聯。當調用“消費者”的consume()方法時,它會新建一個線程並消費“倉庫”中的產品。
(03) Depot是“倉庫”類,倉庫中記錄“倉庫的容量(capacity)”以及“倉庫中當前產品數目(size)”。
        “倉庫”類的生產方法produce()和消費方法consume()方法都是synchronized方法,進入synchronized方法體,意味着這個線程獲取到了該“倉庫”對象的同步鎖。這也就是說,同一時間,生產者和消費者線程只能有一個能運行。通過同步鎖,實現了對“殘酷”的互斥訪問。
       對於生產方法produce()而言:當倉庫滿時,生產者線程等待,需要等待消費者消費產品之後,生產線程才能生產;生產者線程生產完產品之後,會通過notifyAll()喚醒同步鎖上的所有線程,包括“消費者線程”,即我們所說的“通知消費者進行消費”。
      對於消費方法consume()而言:當倉庫爲空時,消費者線程等待,需要等待生產者生產產品之後,消費者線程才能消費;消費者線程消費完產品之後,會通過notifyAll()喚醒同步鎖上的所有線程,包括“生產者線程”,即我們所說的“通知生產者進行生產”。

(某一次)運行結果

複製代碼
Thread-0 produce( 60) --> left=  0, inc= 60, size= 60
Thread-4 produce(110) --> left= 70, inc= 40, size=100
Thread-2 consume( 90) <-- left=  0, dec= 90, size= 10
Thread-3 consume(150) <-- left=140, dec= 10, size=  0
Thread-1 produce(120) --> left= 20, inc=100, size=100
Thread-3 consume(150) <-- left= 40, dec=100, size=  0
Thread-4 produce(110) --> left=  0, inc= 70, size= 70
Thread-3 consume(150) <-- left=  0, dec= 40, size= 30
Thread-1 produce(120) --> left=  0, inc= 20, size= 50

發佈了39 篇原創文章 · 獲贊 52 · 訪問量 10萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章