線程間的通信——wait及notify方法


轉自:http://www.cnblogs.com/mengdd/archive/2013/02/20/2917956.html




線程間的相互作用

  線程間的相互作用:線程之間需要一些協調通信,來共同完成一件任務。

  Object類中相關的方法有兩個notify方法和三個wait方法:

  http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

  因爲wait和notify方法定義在Object類中,因此會被所有的類所繼承。

  這些方法都是final的,即它們都是不能被重寫的,不能通過子類覆寫去改變它們的行爲。

 

wait()方法

  wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法。

  當前的線程必須擁有當前對象的monitor,也即lock,就是鎖。

  線程調用wait()方法,釋放它對鎖的擁有權,然後等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。

  要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。

 

  一個小比較:

  當線程調用了wait()方法時,它會釋放掉對象的鎖。

  另一個會導致線程暫停的方法:Thread.sleep(),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放掉對象的鎖的。

 

notify()方法

  notify()方法會喚醒一個等待當前對象的鎖的線程。

  如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是由於調用了wait方法中的一個)。

  被喚醒的線程是不能被執行的,需要等到當前線程放棄這個對象的鎖。

  被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程並沒有什麼優先權,也沒有什麼劣勢,對象的下一個線程還是需要通過一般性的競爭。

  notify()方法應該是被擁有對象的鎖的線程所調用。

  (This method should only be called by a thread that is the owner of this object's monitor.)

  換句話說,和wait()方法一樣,notify方法調用必須放在synchronized方法或synchronized塊中。

 

  wait()和notify()方法要求在調用時線程已經獲得了對象的鎖,因此對這兩個方法的調用需要放在synchronized方法或synchronized塊中。

  一個線程變爲一個對象的鎖的擁有者是通過下列三種方法:

  1.執行這個對象的synchronized實例方法。

  2.執行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。

  3.對於Class類的對象,執行那個類的synchronized、static方法。

 

程序實例

  利用兩個線程,對一個整形成員變量進行變化,一個對其增加,一個對其減少,利用線程間的通信,實現該整形變量0101這樣交替的變更。

複製代碼
public class NumberHolder
{
    private int number;

    public synchronized void increase()
    {
        if (0 != number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 能執行到這裏說明已經被喚醒
        // 並且number爲0
        number++;
        System.out.println(number);

        // 通知在等待的線程
        notify();
    }

    public synchronized void decrease()
    {
        if (0 == number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }

        // 能執行到這裏說明已經被喚醒
        // 並且number不爲0
        number--;
        System.out.println(number);
        notify();
    }

}



public class IncreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public IncreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行增加操作
            numberHolder.increase();
        }
    }

}



public class DecreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public DecreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行減少操作
            numberHolder.decrease();
        }
    }

}



public class NumberTest
{
    public static void main(String[] args)
    {
        NumberHolder numberHolder = new NumberHolder();
        
        Thread t1 = new IncreaseThread(numberHolder);
        Thread t2 = new DecreaseThread(numberHolder);
                
        t1.start();
        t2.start();
    }

}
複製代碼

  

  如果再多加上兩個線程呢?

  即把其中的NumberTest類改爲如下:

複製代碼
public class NumberTest
{
    public static void main(String[] args)
    {
        NumberHolder numberHolder = new NumberHolder();
        
        Thread t1 = new IncreaseThread(numberHolder);
        Thread t2 = new DecreaseThread(numberHolder);
        
        Thread t3 = new IncreaseThread(numberHolder);
        Thread t4 = new DecreaseThread(numberHolder);
                
        t1.start();
        t2.start();
        
        t3.start();
        t4.start();
    }

}
複製代碼

 

  運行後發現,加上t3和t4之後結果就錯了。

  爲什麼兩個線程的時候執行結果正確而四個線程的時候就不對了呢?

  因爲線程在wait()的時候,接收到其他線程的通知,即往下執行,不再進行判斷。兩個線程的情況下,喚醒的肯定是另一個線程;但是在多個線程的情況下,執行結果就會混亂無序。

  比如,一個可能的情況是,一個增加線程執行的時候,其他三個線程都在wait,這時候第一個線程調用了notify()方法,其他線程都將被喚醒,然後執行各自的增加或減少方法。

  解決的方法就是:在被喚醒之後仍然進行條件判斷,去檢查要改的數字是否滿足條件,如果不滿足條件就繼續睡眠。把兩個方法中的if改爲while即可。

複製代碼
public class NumberHolder
{
    private int number;

    public synchronized void increase()
    {
        while (0 != number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 能執行到這裏說明已經被喚醒
        // 並且number爲0
        number++;
        System.out.println(number);

        // 通知在等待的線程
        notify();
    }

    public synchronized void decrease()
    {
        while (0 == number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }

        // 能執行到這裏說明已經被喚醒
        // 並且number不爲0
        number--;
        System.out.println(number);
        notify();
    }

}


線程間的相互作用

  線程間的相互作用:線程之間需要一些協調通信,來共同完成一件任務。

  Object類中相關的方法有兩個notify方法和三個wait方法:

  http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html

  因爲wait和notify方法定義在Object類中,因此會被所有的類所繼承。

  這些方法都是final的,即它們都是不能被重寫的,不能通過子類覆寫去改變它們的行爲。

 

wait()方法

  wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法。

  當前的線程必須擁有當前對象的monitor,也即lock,就是鎖。

  線程調用wait()方法,釋放它對鎖的擁有權,然後等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。

  要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。

 

  一個小比較:

  當線程調用了wait()方法時,它會釋放掉對象的鎖。

  另一個會導致線程暫停的方法:Thread.sleep(),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放掉對象的鎖的。

 

notify()方法

  notify()方法會喚醒一個等待當前對象的鎖的線程。

  如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是由於調用了wait方法中的一個)。

  被喚醒的線程是不能被執行的,需要等到當前線程放棄這個對象的鎖。

  被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程並沒有什麼優先權,也沒有什麼劣勢,對象的下一個線程還是需要通過一般性的競爭。

  notify()方法應該是被擁有對象的鎖的線程所調用。

  (This method should only be called by a thread that is the owner of this object's monitor.)

  換句話說,和wait()方法一樣,notify方法調用必須放在synchronized方法或synchronized塊中。

 

  wait()和notify()方法要求在調用時線程已經獲得了對象的鎖,因此對這兩個方法的調用需要放在synchronized方法或synchronized塊中。

  一個線程變爲一個對象的鎖的擁有者是通過下列三種方法:

  1.執行這個對象的synchronized實例方法。

  2.執行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。

  3.對於Class類的對象,執行那個類的synchronized、static方法。

 

程序實例

  利用兩個線程,對一個整形成員變量進行變化,一個對其增加,一個對其減少,利用線程間的通信,實現該整形變量0101這樣交替的變更。

複製代碼
public class NumberHolder
{
    private int number;

    public synchronized void increase()
    {
        if (0 != number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 能執行到這裏說明已經被喚醒
        // 並且number爲0
        number++;
        System.out.println(number);

        // 通知在等待的線程
        notify();
    }

    public synchronized void decrease()
    {
        if (0 == number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }

        // 能執行到這裏說明已經被喚醒
        // 並且number不爲0
        number--;
        System.out.println(number);
        notify();
    }

}



public class IncreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public IncreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行增加操作
            numberHolder.increase();
        }
    }

}



public class DecreaseThread extends Thread
{
    private NumberHolder numberHolder;

    public DecreaseThread(NumberHolder numberHolder)
    {
        this.numberHolder = numberHolder;
    }

    @Override
    public void run()
    {
        for (int i = 0; i < 20; ++i)
        {
            // 進行一定的延時
            try
            {
                Thread.sleep((long) Math.random() * 1000);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

            // 進行減少操作
            numberHolder.decrease();
        }
    }

}



public class NumberTest
{
    public static void main(String[] args)
    {
        NumberHolder numberHolder = new NumberHolder();
        
        Thread t1 = new IncreaseThread(numberHolder);
        Thread t2 = new DecreaseThread(numberHolder);
                
        t1.start();
        t2.start();
    }

}
複製代碼

  

  如果再多加上兩個線程呢?

  即把其中的NumberTest類改爲如下:

複製代碼
public class NumberTest
{
    public static void main(String[] args)
    {
        NumberHolder numberHolder = new NumberHolder();
        
        Thread t1 = new IncreaseThread(numberHolder);
        Thread t2 = new DecreaseThread(numberHolder);
        
        Thread t3 = new IncreaseThread(numberHolder);
        Thread t4 = new DecreaseThread(numberHolder);
                
        t1.start();
        t2.start();
        
        t3.start();
        t4.start();
    }

}
複製代碼

 

  運行後發現,加上t3和t4之後結果就錯了。

  爲什麼兩個線程的時候執行結果正確而四個線程的時候就不對了呢?

  因爲線程在wait()的時候,接收到其他線程的通知,即往下執行,不再進行判斷。兩個線程的情況下,喚醒的肯定是另一個線程;但是在多個線程的情況下,執行結果就會混亂無序。

  比如,一個可能的情況是,一個增加線程執行的時候,其他三個線程都在wait,這時候第一個線程調用了notify()方法,其他線程都將被喚醒,然後執行各自的增加或減少方法。

  解決的方法就是:在被喚醒之後仍然進行條件判斷,去檢查要改的數字是否滿足條件,如果不滿足條件就繼續睡眠。把兩個方法中的if改爲while即可。

複製代碼
public class NumberHolder
{
    private int number;

    public synchronized void increase()
    {
        while (0 != number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }

        // 能執行到這裏說明已經被喚醒
        // 並且number爲0
        number++;
        System.out.println(number);

        // 通知在等待的線程
        notify();
    }

    public synchronized void decrease()
    {
        while (0 == number)
        {
            try
            {
                wait();
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }

        }

        // 能執行到這裏說明已經被喚醒
        // 並且number不爲0
        number--;
        System.out.println(number);
        notify();
    }

}

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