Java 併發編程(二) 線程等待與喚醒

一. 前言

線程等待與喚醒主要是線程之間的通訊,java

分析了一下目前有三種,咱們接着分析;bash

  • Object   wait(),notify()
  • Thread   sleep(),interrupt()
  • Condition  await(),signal()

二. Object 的線程等待與喚醒

先舉個例子在說話,省略類的外層了:app

例1:測試

@Test
    public void test6() throws InterruptedException {
        Thread thread8 = new Thread(() -> {
            try {
                synchronized (this){
                    LOG.info("wait");
                    this.wait();
                }
                LOG.info("wake up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread8.start();
        Thread.sleep(3000);
        synchronized (this){
            LOG.info("wake up thread ");
            this.notify();
            Thread.sleep(2000);
        }
    }

例2:this

private final static String SIGN = "sign";

    @Test
    public void test7() throws InterruptedException {
        Thread thread8 = new Thread(() -> {
            try {
                synchronized (SIGN){
                    LOG.info("wait");
                    SIGN.wait();
                }
                LOG.info("wake up");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        thread8.start();
        Thread.sleep(3000);
        synchronized (SIGN){
            LOG.info("wake up thread ");
            SIGN.notify();
            Thread.sleep(2000);
        }
    }

日誌輸出:編碼

2018-08-30 12:31:41.615 myAppName [Thread-0] INFO  com.river.other.ThreadInterruptedTest - wait
2018-08-30 12:31:44.613 myAppName [main] INFO  com.river.other.ThreadInterruptedTest - wake up thread 
2018-08-30 12:31:46.613 myAppName [Thread-0] INFO  com.river.other.ThreadInterruptedTest - wake up

2.1 場景分析

      線程Thread-0首先得到內置鎖,調用wait()方法,使得當前線程處於 WAIT 狀態,此時線程會釋放鎖;spa

     主線程得到鎖,調用監視對象的notify()方法喚醒Thread-0,主線程sleep 2s後釋放鎖,主線程結束運行;.net

     此時Thread-0得到鎖繼續執行,打印go on ,執行完畢,結束運行;線程

2.2 知識點

       咱們查看一下JDK中關於這幾個方法的定義;翻譯

2.2.1 wait() , wait(long timeout), wait(long timeout, int nanos)

使當前線程等待(WAIT)直到另外一個線程調用此對象的notify(),notifyAll()或者一段時間流逝以後;

當前線程必須擁有此對象的監視器;

該方法會使得當前線程(稱之爲T)將本身放置於對象的等待集中,而後放棄此對象上的全部同步要求;線程T對於線程調度變得不可用和休眠直到下面四件事情之一發生:

  • 其餘某個線程調用了此監視對象的notify(),而且線程T剛好被任意的選擇喚醒;
  • 其餘某個線程調用了此監視對象的notifyAll();
  • 其餘某個線程調用此線程interrupt();
  • 指定的時間流逝;若指定時間爲0,則只有當被調用notify(),notifyAll()纔會被喚醒;

線程T被今後監視對象的等待集中移除時,對於線程調用從新變得可用,它接着會與其餘在此監視對象上請求同步權利的線程以常規的方式競爭;一旦該線程得到了此對象的控制,全部在此對象上的同步要求都會被恢復,徹底恢復到調用wait()方法時的樣子;線程T將會從wait()方法的調用中返回;

一個線程也會在不notify(),interrupted或者時間流逝後喚醒,一個所謂的假的喚醒;雖然他不多的發生在實踐中;應用必須在測試中警戒假喚醒的發生的狀況,若是條件不知足繼續處於wait狀態;換句話說,wait應該始終發生在循環中,以下:

synchronized (obj) {
              while (<condition does not hold>)
                  obj.wait(timeout);
              ... // Perform action appropriate to condition
          }

解釋一下上面的翻譯:

       監視對象就是鎖對象,就是說在使用wait()一類方法和notify()一類方法時,必須先得到監視對象的鎖,使用synchronized 關鍵字,synchronized 是一種JVM提供的內置鎖,一般鎖的對象有三種,詳細點擊java 鎖分析 synchronized 和 Lock2.2小結;若在沒有得到監視對象鎖的狀況下調用,則會拋java.lang.IllegalMonitorStateException異常;

       當調用wait()方法時,當前線程會被放置到監視對象的等待集中,同時再也不持有鎖,此時其餘線程能夠得到該監視對象的鎖;線程進入WAIT狀態,當被喚醒時,該線程將與其餘等待相同監視對象鎖的線程同樣,等待CPU分配時間片,競爭鎖,沒有任何特權,該線程執行時,將徹底恢復到調用wait()方法時的狀態,繼續執行接下來的方法;

      我的理解當RUNNING線程中監視對象調用wait()使得現場變爲WAIT狀態,此時線程處於不可用,不可用是針對CPU分配時間片來說的,jdk註釋是這樣講的:

becomes disabled for thread scheduling purposes

也就是說不是線程調度對其無效,而是根本就是disabled,不在調度管理之中,CPU不會分配時間片給他;只有被喚醒纔會被線程調度管理;

       調用wait()方法處於WAIT的線程,會存在被假喚醒的狀況,這種狀況很罕見,但爲了不這種狀況發生致使應用發生未知的異常,建議將wait()方法處於循環體中,如上面的例子,使線程在不知足被喚醒的條件時始終處於WAIT狀態,避免被假喚醒;

      針對上面被喚醒的四種場景,咱們只重點介紹下第三種;其餘三種都屬於常規的喚醒WAIT中的線程,而第三種有一個異常 InterruptedException 須要處理;

package com.river.other;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

@Slf4j
public class ObjectWaitTest {

    @Test
    public void test() throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (this) {
                try {
                    log.info("wait...");
                    this.wait();
                    log.info("go on");
                } catch (InterruptedException e) {
                    log.error("error");
                    e.printStackTrace();
                }
                log.info("go on...");
            }
        });

        thread1.start();
        Thread.sleep(2000);

        log.info("interrupt");
        Thread.sleep(500);
        thread1.interrupt();
    }
}

日誌輸出:

2018-08-30 17:17:38.304 myAppName [Thread-0] INFO  com.river.other.ObjectWaitTest - wait...
2018-08-30 17:17:40.300 myAppName [main] INFO  com.river.other.ObjectWaitTest - interrupt
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at com.river.other.ObjectWaitTest.lambda$test$0(ObjectWaitTest.java:15)
	at java.lang.Thread.run(Thread.java:745)
2018-08-30 17:17:40.800 myAppName [Thread-0] ERROR com.river.other.ObjectWaitTest - error
2018-08-30 17:17:40.801 myAppName [Thread-0] INFO  com.river.other.ObjectWaitTest - go on...

       經過日誌分析可看到,處於WAIT狀態的線程被調用interrupt()方法後,會當即拋出InterruptedException異常,該異常爲檢查異常,必須由編碼處理的異常,catch異常以後線程會繼續執行後面的操做;

       忽然想到了sleep();

@Test
    public void test() throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            synchronized (this) {
                try {
                    log.info("wait...");
                    //this.wait();
                    Thread.sleep(10000);
                    log.info("go on");
                } catch (InterruptedException e) {
                    log.error("error");
                    e.printStackTrace();
                }
                log.info("go on...");
            }
        });

        thread1.start();
        Thread.sleep(2000);
        log.info("interrupt");
        Thread.sleep(500);
        thread1.interrupt();
    }

日誌輸出:

2018-08-30 17:27:28.955 myAppName [Thread-0] INFO  com.river.other.ObjectWaitTest - wait...
2018-08-30 17:27:30.954 myAppName [main] INFO  com.river.other.ObjectWaitTest - interrupt

2018-08-30 17:27:31.454 myAppName [Thread-0] ERROR com.river.other.ObjectWaitTest - error
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at com.river.other.ObjectWaitTest.lambda$test$0(ObjectWaitTest.java:16)
	at java.lang.Thread.run(Thread.java:745)
2018-08-30 17:27:31.454 myAppName [Thread-0] INFO  com.river.other.ObjectWaitTest - go on...

咱們看到日誌輸出與上面無異;

      那麼這裏使用wait()和sleep()有什麼區別呢?

2.2.2 wait()和sleep()

wait() 用於必須得到監視對象鎖以後執行,wait()方法調用後,會讓出持有的鎖供其餘線程競爭得到;

          一般用notify()喚醒,必須在得到同一監視對象鎖在能夠調用notify喚醒線程;

sleep()  使用沒有限制,被調用後是當前線程讓出CPU時間片,若在得到鎖以後執行,則不會釋放鎖;

上面二者被interrupt打斷後都會拋出InterruptedException異常須要處理;

2.2.3 notify() notifyAll()

          notify用於喚醒當前監視對象等待集中WAIT的線程,若等待集中有多個線程,則調用notifyAll()將其所有喚醒,若此時調用notify(),則會喚醒任意一個線程(jdk中用詞arbitrary),該方法不會釋放持有的鎖;

三. Thread sleep() interrupt()

        該處知識點已在上面有過對比介紹,不在重複了;

四. Condition  await(),signal()

       請在另外一篇文章java 鎖分析 synchronized 和 Lock 3.1.4小節中查看;

五. 小結

    總的來說,二和四主要是線程間通訊的,使用原理基本一致,都須要在得到監視對象的鎖的狀況下才能夠調用方法,爲什麼如此呢?猜測既然是線程間通訊,確定是對同一資源或相關資源進行訪問,線程之間彼此有影響,否則也不須要相互間保持通訊,既然有影響,那麼就須要同步,因此須要串行執行操做;

相關文章
相關標籤/搜索