線程喚醒等待機制

在Java最早期的時候,常常採用suspend()和resume()方法對線程進行阻塞和喚醒,但是現在不再推薦使用了,是因爲:
suspend()方法在導致線程阻塞的過程中,不會釋放任何鎖資源。其他線程都無法訪問被它佔用的鎖。直到對應的線程執行resume()方法後,被掛起的線程才能繼續,從而其他被阻塞在這個鎖的線程才能繼續執行。
如果resume操作出現在suspend操作之前,那麼線程就會一直處於阻塞狀態–>產生死鎖,但是對於被掛起的線程,他的線程仍舊是Runnable狀態

實例
class SuspendResumeTest {
    public static Object object = new Object();

    public static class TestThread extends Thread {
        public TestThread(String name) {
            super.setName(name);
        }

        @Override
        public void run() {
            synchronized (object) {
                System.out.println(getName() + "佔用。。。");
                Thread.currentThread().suspend();
            }
        }
    }

    static TestThread t1 = new TestThread("我是線程1");
    static TestThread t2 = new TestThread("我是線程2");

    public static void main(String[] args) throws InterruptedException {
        t1.start();
        //Thread.sleep(200);
        t2.start();
        t1.resume();
        t2.resume();
        t1.join();
        t2.join();
    }
}
執行結果:

在這裏插入圖片描述
產生死鎖!!!

接下來讓我們來說一下,常見的,推薦的wait和notify方法進行阻塞和喚醒吧~

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

  • notify:喚醒在此對象監視器上等待的單個線程
  • notifyAll:喚醒在此對象監視器上等待的所有線程
  • wait:讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,當前線程被喚醒
  • wait(long timeout):讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,或者超過指定的時間,當前線程被喚醒
  • wait(long timeout, int nanos):讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,或者其他某個線程中斷當前線程,或者已經超過某個實際時間量,當前線程被喚醒
實例
public class WaitTest {
    public static void main(String[] args) {
        ThreadA t1=new ThreadA("t1");

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

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

                System.out.println(Thread.currentThread().getName()+"continue");
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
class ThreadA extends Thread{

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

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

######執行結果
在這裏插入圖片描述

結果說明

在這裏插入圖片描述

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

####擴展一下wait()–>wait(long timeout)

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
結果說明

和上面的wait方法類似,只不過主線程不會立刻被喚醒,而是等待3000ms後喚醒

擴展一下notify–>notifyAll

public class NotifyAllTest {

    private static Object obj = new Object();
    public static void main(String[] args) {

        ThreadA t1 = new ThreadA("t1");
        ThreadA t2 = new ThreadA("t2");
        ThreadA t3 = new ThreadA("t3");
        t1.start();
        t2.start();
        t3.start();

        try {
            System.out.println(Thread.currentThread().getName()+" sleep(3000)");
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized(obj) {
            // 主線程等待喚醒。
            System.out.println(Thread.currentThread().getName()+" notifyAll()");
            obj.notifyAll();
        }
    }

    static class ThreadA extends Thread{

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

        public void run() {
            synchronized (obj) {
                try {
                    // 打印輸出結果
                    System.out.println(Thread.currentThread().getName() + " wait");

                    // 喚醒當前的wait線程
                    obj.wait();

                    // 打印輸出結果
                    System.out.println(Thread.currentThread().getName() + " continue");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
執行結果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
結果說明
  • 主線程中新建並啓動了3個線程"t1" “t2” “t3”
  • 主線程通過sleep(3000)休眠3秒,在主線程休眠3秒的過程中,我們假設"t1" “t2” “t3” 這3個線程都運行了,以"t1"爲例,當它運行的時候,它會執行obj.wait()等待其他線程通過notify或者notifyAll方法來喚醒她,同樣的道理,t2和t3也會等待其他線程通過notify或者notifyAll來喚醒它們
  • 主線程休眠3秒後,接着運行,執行obj.notifyAll()喚醒obj上的等待線程,即喚醒"t1" “t2” "t3"這3個線程,緊接着,主線程的synchronized(obj)運行完畢後,主動釋放obj鎖,這樣t1 t2 t3就可以獲取鎖資源接着運行了。

notify() wait()等函數定義在object中,而不是Thread中的原因

Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。
wait()會使“當前線程”等待,因爲線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!

LockSupport提供的park和unpark方法

這兩個方法是基於suspend和resume這一組合的基礎上演變過來的,提供避免死鎖和競態條件

public class ThreadParkTest {
    public static void main(String[] args) {
        MyThread mt = new MyThread();
        mt.setName("mt");
        mt.start();
        try {
            Thread.currentThread().sleep(10);
            mt.park();
            Thread.currentThread().sleep(30000);
            mt.unPark();
            Thread.currentThread().sleep(30000);
            mt.park();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    static class MyThread extends Thread {

        private boolean isPark = false;
        public void run() {
            System.out.println(" Enter Thread running.....");
            while (true) {
                if (isPark) {
                    System.out.println("Thread is Park.....");
                    LockSupport.park();
                }
            }
        }
        public void park() {
            isPark = true;
        }
        public void unPark() {
            isPark = false;
            LockSupport.unpark(this);
            System.out.println("Thread is unpark.....");
        }
    }
}

爲什麼使用park和unpark方法可以避免死鎖的產生?

park和unpark方法控制的粒度更細,能夠準確的決定線程在某個點停止
引入了許可機制,邏輯爲:
1、park講許可在等於0的時候阻塞,等於1的時候返回並將許可減爲0
2、unpark嘗試喚醒線程,許可加1。根據這兩個邏輯,對於同一條線程,park與unpark先後操作的順序似乎並不影響程序正確地執行,假如先執行unpark操作,許可則爲1,之後再執行park操作,此時因爲許可等於1直接返回往下執行,並不執行阻塞操作。

總結總結總結
suspend()、resume()已經被deprecated,不建議使用。wait與notify要保證必須有鎖才能執行,而且執行notify操作釋放鎖後還要將當前線程扔進該對象鎖的等待隊列。LockSupport則完全不用考慮對象、鎖、等待隊列,真正解耦合線程之間的同步。

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