程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> JAVA多線程之wait/notify,多線程waitnotify

JAVA多線程之wait/notify,多線程waitnotify

編輯:JAVA綜合教程

JAVA多線程之wait/notify,多線程waitnotify


本文主要學習JAVA多線程中的 wait()方法 與 notify()/notifyAll()方法的用法。

①wait() 與 notify/notifyAll 方法必須在同步代碼塊中使用

②wait() 與  notify/notifyAll() 的執行過程

③中斷 調用wait()方法進入等待隊列的 線程

④notify 通知的順序不能錯

⑤多線程中測試某個條件的變化用 if 還是用 while?

 

 

①wait() 與 notify/notifyAll 方法必須在同步代碼塊中使用

wait() 與 notify/notifyAll() 是Object類的方法,在執行兩個方法時,要先獲得鎖。那麼怎麼獲得鎖呢?

在這篇:JAVA多線程之Synchronized關鍵字--對象鎖的特點文章中介紹了使用synchronized關鍵字獲得鎖。因此,wait() 與  notify/notifyAll() 經常與synchronized搭配使用,即在synchronized修飾的同步代碼塊或方法裡面調用wait() 與  notify/notifyAll()方法。

 

②wait() 與  notify/notifyAll() 的執行過程

由於 wait() 與  notify/notifyAll() 是放在同步代碼塊中的,因此線程在執行它們時,肯定是進入了臨界區中的,即該線程肯定是獲得了鎖的。

當線程執行wait()時,會把當前的鎖釋放,然後讓出CPU,進入等待狀態。

 當執行notify/notifyAll方法時,會喚醒一個處於等待該 對象鎖 的線程,然後繼續往下執行,直到執行完退出對象鎖鎖住的區域(synchronized修飾的代碼塊)後再釋放鎖。

從這裡可以看出,notify/notifyAll()執行後,並不立即釋放鎖,而是要等到執行完臨界區中代碼後,再釋放。故,在實際編程中,我們應該盡量在線程調用notify/notifyAll()後,立即退出臨界區。即不要在notify/notifyAll()後面再寫一些耗時的代碼。

示例如下:(摘自《JAVA多線程核心技術》)

 1 public class Service {
 2 
 3     public void testMethod(Object lock) {
 4         try {
 5             synchronized (lock) {
 6                 System.out.println("begin wait() ThreadName="
 7                         + Thread.currentThread().getName());
 8                 lock.wait();
 9                 System.out.println("  end wait() ThreadName="
10                         + Thread.currentThread().getName());
11             }
12         } catch (InterruptedException e) {
13             e.printStackTrace();
14         }
15     }
16 
17     public void synNotifyMethod(Object lock) {
18         try {
19             synchronized (lock) {
20                 System.out.println("begin notify() ThreadName="
21                         + Thread.currentThread().getName() + " time="
22                         + System.currentTimeMillis());
23                 lock.notify();
24                 Thread.sleep(5000);
25                 System.out.println("  end notify() ThreadName="
26                         + Thread.currentThread().getName() + " time="
27                         + System.currentTimeMillis());
28             }
29         } catch (InterruptedException e) {
30             e.printStackTrace();
31         }
32     }
33 }

在第3行的testMethod()中調用 wait(),在第17行的synNotifyMethod()中調用notify()

從上面的代碼可以看出,wait() 與  notify/notifyAll()都是放在同步代碼塊中才能夠執行的。如果在執行wait() 與  notify/notifyAll() 之前沒有獲得相應的對象鎖,就會拋出:java.lang.IllegalMonitorStateException異常。

在第8行,當ThreadA線程執行lock.wait();這條語句時,釋放獲得的對象鎖lock,並放棄CPU,進入等待隊列。

當另一個線程執行第23行lock.notify();,會喚醒ThreadA,但是此時它並不立即釋放鎖,接下來它睡眠了5秒鐘(sleep()是不釋放鎖的,事實上sleep()也可以不在同步代碼塊中調用),直到第28行,退出synchronized修飾的臨界區時,才會把鎖釋放。這時,ThreadA就有機會獲得另一個線程釋放的鎖,並從等待的地方起(第24行)起開始執行。

 

接下來是兩個線程類,線程類ThreadA調用testMethod()方法執行lock.wait();時被掛起,另一個線程類synNotifyMethodThread調用synNotifyMethod()負責喚醒掛起的線程。代碼如下:

 

 1 public class ThreadA extends Thread {
 2     private Object lock;
 3 
 4     public ThreadA(Object lock) {
 5         super();
 6         this.lock = lock;
 7     }
 8 
 9     @Override
10     public void run() {
11         Service service = new Service();
12         service.testMethod(lock);
13     }
14 }
15 
16 public class synNotifyMethodThread extends Thread {
17     private Object lock;
18 
19     public synNotifyMethodThread(Object lock) {
20         super();
21         this.lock = lock;
22     }
23 
24     @Override
25     public void run() {
26         Service service = new Service();
27         service.synNotifyMethod(lock);
28     }
29 }

 

 

再接下來是測試類:

 1 public class Test {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4 
 5         Object lock = new Object();
 6 
 7         ThreadA a = new ThreadA(lock);
 8         a.start();
 9 
10         NotifyThread notifyThread = new NotifyThread(lock);
11         notifyThread.start();
12 
13         synNotifyMethodThread c = new synNotifyMethodThread(lock);
14         c.start();
15     }
16 }

 

③中斷 調用wait()方法進入等待隊列的 線程

示例代碼如下:

 1 public class Service {
 2 
 3     public void testMethod(Object lock) {
 4         try {
 5             synchronized (lock) {
 6                 System.out.println("begin wait()");
 7                 lock.wait();
 8                 System.out.println("  end wait()");
 9             }
10         } catch (InterruptedException e) {
11             e.printStackTrace();
12             System.out.println("出現異常");
13         }
14     }
15 }
16 
17 public class ThreadA extends Thread {
18 
19     private Object lock;
20 
21     public ThreadA(Object lock) {
22         super();
23         this.lock = lock;
24     }
25 
26     @Override
27     public void run() {
28         Service service = new Service();
29         service.testMethod(lock);
30     }
31 }

注意,在第23行wait()方法是Object類的對象lock調用的。而下面的interrupt()方法是ThreadA類的對象調用的。在ThreadA裡面,將Object的對象作為參數傳給了testMethod()方法,ThreadA的run()方法去調用testMethod(),從而wait()使ThreadA的線程暫停了(暫停當前執行wait()的線程)。從這裡可以看出一個區別:

Object類中與線程有關的方法:

1)notify/notifyAll

2)wait()/wait(long)

java.lang.Thread中與之相關的方法:

1)interrupt()

2)sleep()/sleep(long)

3)join()/suspend()/resume()....

測試類代碼如下:

 1 public class Test {
 2 
 3     public static void main(String[] args) {
 4 
 5         try {
 6             Object lock = new Object();
 7 
 8             ThreadA a = new ThreadA(lock);
 9             a.start();
10 
11             Thread.sleep(5000);
12 
13             a.interrupt();
14         } catch (InterruptedException e) {
15             e.printStackTrace();
16         }
17     }
18 }

當執行第13行的interrupt()時,處於wait中的線程“立即”被喚醒(一般是立即響應中斷請求),並拋出異常。此時,線程也就結束了。

 

④notify 通知的順序不能錯

假設在線程A中執行wait(),在線程B中執行notify()。但如果線程B先執行了notify()然後結束了,線程A才去執行wait(),那此時,線程A將無法被正常喚醒了(還可以通過③中提到的interrupt()方法以拋出異常的方式喚醒^~^)。

這篇文章: JAVA多線程之線程間的通信方式中的第③點提到了notify通知順序出錯會導致 調用wait()進入等待隊列的線程再也無法被喚醒了。

 

⑤多線程中測試某個條件的變化用 if 還是用 while?

以前一直不明白 當在線程的run()方法中需要測試某個條件時,為什麼用while,而不用if???直到看到了這個簡單的例子,終於明白了。。。。

這個例子是這樣的:

有兩個線程從List中刪除數據,而只有一個線程向List中添加數據。初始時,List為空,只有往List中添加了數據之後,才能刪除List中的數據。添加數據的線程向List添加完數據後,調用notifyAll(),喚醒了兩個刪除線程,但是它只添加了一個數據,而現在有兩個喚醒的刪除線程,這時怎麼辦??

如果用 if 測試List中的數據的個數,則會出現IndexOutofBoundException,越界異常。原因是,List中只有一個數據,第一個刪除線程把數據刪除後,第二個線程再去執行刪除操作時,刪除失敗,從而拋出 IndexOutofBoundException。

但是如果用while 測試List中數據的個數,則不會出現越界異常!!!神奇。

當wait等待的條件發生變化時,會造成程序的邏輯混亂---即,List中沒有數據了,再還是有線程去執行刪除數據的操作。因此,需要用while循環來判斷條件的變化,而不是用if。

示例如下:摘自《JAVA多線程編程核心技術》

Add類,負責添加數據:

public class Add {

    private String lock;
    public Add(String lock) {
        super();
        this.lock = lock;
    }

    public void add() {
        synchronized (lock) {
            ValueObject.list.add("anyString");
            lock.notifyAll();
        }
    }
}

public class ThreadAdd extends Thread {

    private Add p;

    public ThreadAdd(Add p) {
        super();
        this.p = p;
    }

    @Override
    public void run() {
        p.add();
    }
}

 

Subtract類,負責刪除數據----先要進行條件判斷,然後執行wait(),這意味著:wait等待的條件可能發生變化!!!

public class Subtract {

    private String lock;

    public Subtract(String lock) {
        super();
        this.lock = lock;
    }

    public void subtract() {
        try {
            synchronized (lock) {
                if(ValueObject.list.size() == 0) {//將這裡的if改成while即可保證不出現越界異常!!!!
                    System.out.println("wait begin ThreadName="
                            + Thread.currentThread().getName());
                    lock.wait();
                    System.out.println("wait   end ThreadName="
                            + Thread.currentThread().getName());
                }
                ValueObject.list.remove(0);
                System.out.println("list size=" + ValueObject.list.size());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadSubtract extends Thread {

    private Subtract r;

    public ThreadSubtract(Subtract r) {
        super();
        this.r = r;
    }

    @Override
    public void run() {
        r.subtract();
    }
}

 

封裝的List隊列:

public class ValueObject {

    public static List list = new ArrayList();

}

 

測試類:

public class Run {

    public static void main(String[] args) throws InterruptedException {

        String lock = new String("");

        Add add = new Add(lock);
        Subtract subtract = new Subtract(lock);

        ThreadSubtract subtract1Thread = new ThreadSubtract(subtract);
        subtract1Thread.setName("subtract1Thread");
        subtract1Thread.start();

        ThreadSubtract subtract2Thread = new ThreadSubtract(subtract);
        subtract2Thread.setName("subtract2Thread");
        subtract2Thread.start();

        Thread.sleep(1000);

        ThreadAdd addThread = new ThreadAdd(add);
        addThread.setName("addThread");
        addThread.start();

    }
}

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved