Java多線程 -- 線程通信

Java多線程 – 線程通信

一、傳統的線程通信

假設現在系統中有兩個線程,交替打印奇偶數。
爲了實現這個功能,可以藉助於Object類的wait(),notify(),notifyAll()三個方法,這三個方法並不屬於Thread類,而是屬於Object類。但是這三個方法必須由同步監視器對象來進行調用。

可以分爲兩種情況:

  1. 對於使用了synchronized修飾的同步方法,因爲該類的默認實例(this)就是同步監視器,所以可以在同步方法之中直接調用三個方法。
  2. 對於使用了synchronized修飾的同步代碼塊,同步監視器是synchronized後括號裏的對象,所以必須使用對象調用這三個方法。

這三種方法:

  • wait():導致當前線程等待,直到其他線程調用該同步監視器的notify()方法或者notifyAll()方法來喚醒該線程,其中wait()方法有三種形式,無參數的(一直等待,直到其他線程通知),帶毫秒的,以及帶微妙的參數,(這兩個方法是等待指定時間後自動屬性),調用wait()方法的當前線程會釋放對該同步監視器的鎖定。
  • notify():喚醒在此同步監視器上等待的耽擱線程,如果說所有的線程都在此同步監視器上等待,則會喚醒其中一個線程,選擇是任意性的。只有當前線程放棄對該同步監視器的鎖定後(使用wait()方法),纔可以執行被喚醒的進程。
  • notifyAll():喚醒在此同步監視器上等待的所有線程。只有當前線程放棄對該同步監視器的鎖定後,纔可以執行被喚醒的進程。

public class Demo {
    public int start = 1;//定義共享變量
    public boolean flag = false;//定義標識

    public static void main(String[] args) {
        Demo d = new Demo();//新建一個對象實例
        OddNum oddNum = new OddNum(d);
        EvenNum evenNum = new EvenNum(d);
        Thread oddNumThread = new Thread(oddNum);
        Thread evenNumThread = new Thread(evenNum);
        oddNumThread.start();
        evenNumThread.start();
    }



}


class OddNum implements Runnable{
    private Demo demo;
    OddNum(Demo demo){
        this.demo = demo;
    }
    @Override
    public void run(){
        while (demo.start < 100){
            synchronized (Demo.class){
                if(!demo.flag){
                    System.out.println("奇數線程——" + demo.start);
                    demo.start++;
                    demo.flag = true;
                    Demo.class.notify();//讓另一線程甦醒
                }else{
                    try {
                        Demo.class.wait();//本線程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

class EvenNum implements Runnable{
    private Demo demo;
    EvenNum(Demo demo){
        this.demo = demo;
    }
    @Override
    public void run(){
        while(demo.start < 100){
            synchronized (Demo.class){
                if(demo.flag){
                    System.out.println("偶數線程——" + demo.start);
                    demo.start++;
                    demo.flag = false;
                    Demo.class.notify();
                }else{
                    try{
                        Demo.class.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

二、使用Condition控制線程通信

如果程序不採用synchronized關鍵字來保證同步,而是採用Lock對象來保證同步,則系統中不存在隱式的同步監視器,也就不可以使用wait(),notify(),notifyAll()方法進行線程通信了。
但是Java使用了Condition類來保持協調,使用Condition可以讓那些已經得到的Lock對象無法繼續執行線程數釋放Lock對象,Condition對象也可以喚醒其他在等待的線程、
Condition將同步監視器方法(wait()notify()notifyAll())分解成截然不同的對象,以便通過將這些對象與Lock對象組合使用,爲每個對象提供多個等待集(wait-set),這種情況之下,Lock替代了同步方法或同步代碼塊,Condition替代了同步監視器的功能。

Condition實例被綁定在一個Lock對象上,要獲得特定Lock實例的Condition實例,調用Lock對象的newCondition()方法即可,Condition類提供了三個方法:

  • await():類似隱式同步監視器上的wait()方法,導致當前線程等待,指導其他線程調用該Conditionsignal()sianalAll()方法來喚醒該線程。
  • signal():喚醒在此Lock對象上等待的單個線程。如果所有線程都在該Lock對象上等待,則會選擇喚醒其中一個線程,選擇是任意性的,只有當前線程放棄對該Lock對象的鎖定後(await()方法),纔可以執行被喚醒的線程。
  • signalAll():喚醒在此Lock對象上等待的所有線程,只有當前線程放棄對該Lock對象的鎖定後(await()方法),纔可以執行被喚醒的線程。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Demo{
    Lock lock = new ReentrantLock();
    Condition oddCondition = lock.newCondition();
    Condition evenCondition = lock.newCondition();
    boolean flag = false;
    int start = 1;

    public static void main(String[] args) {
        Demo demo = new Demo();
        Thread odd = new Thread(new OddNum(demo));
        Thread even = new Thread(new EvenNum(demo));
        odd.start();
        even.start();
    }
}

class OddNum implements Runnable{
    private Demo demo;

    OddNum(Demo demo){
        this.demo = demo;
    }

    @Override
    public void run(){
        while (demo.start < 100){
            if(!demo.flag){
                demo.lock.lock();
                System.out.println(demo.start);
                demo.start++;
                demo.evenCondition.signal();
            }else{
                try {
                    demo.oddCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    demo.lock.unlock();
                }
            }
        }
    }
}

class EvenNum implements Runnable{
    private Demo demo;

    EvenNum(Demo demo){
        this.demo = demo;
    }

    @Override
    public void run(){
        while (demo.start < 100){
            if(demo.flag){
                demo.lock.lock();
                System.out.println(demo.start);
                demo.start++;
                demo.oddCondition.signal();
            }else{
                try {
                    demo.evenCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    demo.lock.unlock();
                }
            }
        }
    }
}

三、使用阻塞隊列(BlockingQueue)控制線程通信

Java 5提供了一個BlockingQueue藉口,雖然BlockingQueue也是Queue的子接口,但它主要用途不是容易,而是作爲線程同步的工具。BlockingQueue具有一個特徵:當生產者試圖向其中放入元素的時候,如果該隊列已經滿了,則線程會阻塞,當消費者試圖從其中去除元素,當隊列爲空,則該線程被阻塞。
BlockingQueue提供如下兩個支持阻塞的方法:
put(E e):將E元素放入,如果已滿,阻塞該線程。
take():嘗試從頭部去除元素,如果已空,則阻塞之。

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