1. 程式人生 > >第3章 執行緒間通訊

第3章 執行緒間通訊

等待/通知機制

執行緒於執行緒之間不是獨立的個體,它們彼此之間可以相互通訊與協作。

等待/通知機制的實現

方法 wait() 的作用是使當前執行程式碼的執行緒進行等待,wait() 方法使 Object 類的方法,該方法將當前執行緒置入“預執行佇列中”,並且在 wait() 所執行程式碼處停止執行,知道接到通知或被中斷為止。在呼叫 wait() 之前,執行緒必須獲得該物件的物件級別鎖。在執行 wait() 方法後,當前執行緒釋放鎖。
方法 notify() 也要在同步方法或同步塊中呼叫,即在呼叫前,執行緒也必須獲得該物件的物件級別鎖。該方法用來通知那些可能等待該物件的物件鎖的其他執行緒,如果由多個等待執行緒,則由多執行緒規劃器隨機挑選出一個呈 wait() 狀態的執行緒,對其發出通知 notify,並使它等待獲取該物件的物件鎖。
wait 使執行緒停止執行,而 notify 使停止的執行緒繼續執行。

public class MyThread1 extends Thread {
    private Object lock;

    public MyThread1(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                System.out.println("開始 wait time = " + System.
currentTimeMillis()); lock.wait(); System.out.println("結束 wait time = " + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class MyThread2 extends Thread { private Object lock;
public MyThread2(Object lock) { super(); this.lock = lock; } @Override public void run() { synchronized (lock){ System.out.println("開始 notify time = " + System.currentTimeMillis()); lock.notify(); System.out.println("結束 notify time = " + System.currentTimeMillis()); } } } public class Test { public static void main(String[] args) { try { Object lock = new Object(); MyThread1 t1 = new MyThread1(lock); t1.start(); Thread.sleep(3000); MyThread2 t2 = new MyThread2(lock); t2.start(); } catch (InterruptedException e) { e.printStackTrace(); } } }

執行結果:
開始 wait time = 1543231514059
開始 notify time = 1543231517064
結束 notify time = 1543231517064
結束 wait time = 1543231517064

public class MyList {
    private static List list = new ArrayList();
    public static void add(){
        list.add("anyString");
    }
    public static int size(){
        return list.size();
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                if(MyList.size() != 5){
                    System.out.println("wait begin " + System.currentTimeMillis());
                    lock.wait();
                    System.out.println("wait end " + System.currentTimeMillis());
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            synchronized (lock){
                for (int i = 0; i < 10; i++) {
                    MyList.add();
                    if(MyList.size() == 5){
                        //根據執行結果來看,執行notify以後,並不是立即釋放鎖,因為這個方法還在繼續執行
                        lock.notify();
                        System.out.println("已發出通知!");
                    }
                    System.out.println("添加了 " + (i+1) + "個元素");
                    Thread.sleep(1000);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class Run {
    public static void main(String[] args) {
        try {
            Object lock = new Object();
            ThreadA threadA = new ThreadA(lock);
            threadA.start();
            Thread.sleep(50);
            ThreadB threadB = new ThreadB(lock);
            threadB.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

關鍵字 synchronized 可以將任何一個 Object 物件作為同步物件,而 Java 為每個 Object 都實現了 wait() 和 notify() 方法。它們必須用在被 synchronized 同步的 Object 臨界區內。通過呼叫 wait() 方法可以使處於臨界區內的執行緒進入等待狀態,同時釋放被同步的物件鎖。而 notify 操作可以喚醒一個因呼叫了 wait 操作而處於阻塞狀態中的執行緒,使其進入就緒狀態。
notify() 方法可以隨機喚醒等待佇列中等待同一個資源的一個執行緒,並使該執行緒退出等待佇列,進入可執行狀態,也就是 notify() 方法僅通知一個執行緒。
notifyAll() 方法可以使所有正在等待佇列中等待同一資源的全部執行緒從等待狀態退出,進入可執行狀態。此時,優先順序最高的那個執行緒最先執行,但也有可能隨機執行,這取決於 JVM 虛擬機器的實現。

在這裡插入圖片描述

  1. 新建立一個執行緒後再呼叫它的 start() 方法,系統會為此執行緒分配 CPU 資源,使其處於 Runnable(可執行)狀態,這是一個準備執行的階段。如果執行緒搶到 CPU 資源,該執行緒就處於 Running(執行)狀態。
  2. Running 狀態和 Runnable 狀態可相互切換,因為有可能執行緒執行一段時間以後,有其他優先順序高的執行緒搶佔了 CPU 資源,此時執行緒就從 Running 變成了 Runnable。
    執行緒進入 Runnable 狀態大體分為 5 中情況:
  • 呼叫 sleep() 方法後經過的時間超過了指定的休眠時間
  • 執行緒呼叫的阻塞 IO 已經返回,阻塞方法執行完畢
  • 執行緒成功地獲得了試圖同步的監視器
  • 執行緒正在等待某個通知,其他執行緒發出了通知
  • 處於掛起狀態的執行緒呼叫了 resume 恢復方法
  1. Blocked 是阻塞的意思,例如遇到一個 IO 操作,此時 CPU 處於空閒狀態,可能轉而會把 CPU 時間分配給其他執行緒,這時也稱之為暫停狀態。
    出現阻塞大體分為 5 中情況:
  • 執行緒呼叫 sleep 方法,主動放棄佔用的處理器資源
  • 執行緒呼叫了阻塞的 IO 方法,在方法返回前,該執行緒被阻塞
  • 執行緒試圖獲得一個同步監視器,但該同步監視器正在被其他執行緒所持有
  • 執行緒等待某個通知
  • 程式呼叫了 suspend 方法將該執行緒掛起
  1. run() 方法執行結束後進入銷燬階段,整個執行緒執行完畢。

每個所物件都有兩個佇列,一個是就緒佇列,一個是阻塞佇列。就緒佇列儲存了將要得鎖的執行緒,阻塞佇列儲存了被阻塞的執行緒。

方法 wait() 鎖釋放與 notify() 鎖不釋放

當方法 wait() 被執行後,鎖自動釋放,但執行完 notify() 方法,鎖卻不自動釋放。

wait() 方法被執行以後,鎖自動釋放:

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println(Thread.currentThread().getName() + "begin wait");
                lock.wait();
                System.out.println("end wait");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class ThreadB extends Thread {
    private Object lock;

    public ThreadB(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class Test {
    public static void main(String[] args) {
        Object lock = new Object();
        ThreadA a = new ThreadA(lock);
        a.start();
        ThreadB b = new ThreadB(lock);
        b.start();
    }
}

執行結果:
Thread-0begin wait
Thread-1begin wait

方法 notify() 執行後,不釋放鎖:

public class Service {
    public void testMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin wait ThreadName = " + Thread.currentThread().getName());
                lock.wait();
                System.out.println("end wait ThreadName = " + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void synNotifyMethod(Object lock){
        try {
            synchronized (lock){
                System.out.println("begin notify ThreadName = " + Thread.currentThread().getName() +
                        "time = " + System.currentTimeMillis());
                //lock.notify()這句程式碼執行完畢以後還是不會釋放鎖,要等到notify所在的程式碼塊中的程式碼全部執行完畢以後才會釋放鎖
                lock.notify();
                Thread.sleep(5000);
                System.out.println("end notify ThreadName = " + Thread.currentThread().getName() +
                        "time = " + System.currentTimeMillis());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ThreadA extends Thread {
    private Object lock;

    public ThreadA(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.testMethod(lock);
    }
}

public class NotifyThread extends Thread{
    private Object lock;

    public NotifyThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}

public class synNotifyMethodThread extends Thread {
    private Object lock;

    public synNotifyMethodThread(Object lock) {
        super();
        this.lock = lock;
    }

    @Override
    public void run() {
        Service service = new Service();
        service.synNotifyMethod(lock);
    }
}

public class Test {
    public static void main(String[] args) throws InterruptedException{
        Object lock = new Object();
        ThreadA threadA = new ThreadA(lock);
        threadA.start();
        NotifyThread notifyThread = new NotifyThread(lock);
        notifyThread.start();
        synNotifyMethodThread c = new synNotifyMethodThread(lock);
        c.start();
    }
}

只通知一個執行緒

呼叫 notify() 一次只隨機通知一個執行緒進行喚醒