淺析Java并發編程(三)線程的狀態&協作

前言

搞清楚Java中線程都有哪些狀態,線程間如何進行協作,這是使用Java進行并發編程的基礎。本文是作者自己對Java中線程的狀態、線程間協作、相關API使用的理解與總結,不對之處,望指出,共勉。

線程的狀態

  • NEW

    新建狀態,表示線程已創建,但未啟動。
  • RUNNABLE

    就緒狀態,表示線程已被JVM執行,但是可能還沒獲得操作系統CPU資源的調度,需要等待一會兒(轉瞬之間)。
  • BLOCKED

    阻塞狀態,表示線程被阻塞,可能正在嘗試進入synchronized關鍵字修飾的同步代碼塊、方法,等待獲取監視器鎖(monitor )。
  • WAITING

    等待狀態,通常調用Object.wait()會進入該狀態,直到其他線程調用Object.notify()Object.notifyAll()才會喚醒。
  • TIMED_WAITING

    有限時間的等待狀態,通常調用Object.wait(long timeout)Object.wait(long timeout, int nanos)會進入該狀態,與等待狀態類似,區別就是時間到了之后如果沒有被Object.notify()Object.notifyAll()喚醒的話會自動喚醒。
  • TERMINATED

    終止狀態,線程已經執行完畢。

線程的協作

線程間的協作主要通過java.lang.Object的成員方法wait()notify()notifyAll()java.lang.Thread的靜態方法sleep(long millis)yield()join()等進行。

  • wait()
    使一個線程處于等待狀態,并且釋放所持有的對象的監視器鎖,進入等待池直到notify或notifyAll方法來喚醒。調用此方法要處理java.lang.InterruptedException異常。
  • wait(long timeout)
    如上,區別在于可以自行設置最大等待時間(毫秒),到時間沒有被喚醒則自動喚醒。
  • wait(long timeout, int nanos)
    如上,提供更精確的等待時間(納秒)。
public class WaitTest {

    public synchronized void work() {
        System.out.println("Begin Work");
        try {
            //等待1000毫秒后,自動喚醒繼續執行
            wait(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Work End");
    }

    public static void main(String[] args) {
        WaitTest test = new WaitTest();

        new Thread(() -> test.work()).start();
    }
}
  • notify()
    喚醒一個處于等待狀態的線程,當然在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且與優先級無關。
  • notifyAll()
    喚醒所有處于等待狀態的線程,該方法并不是將對象的監視器鎖給所有線程,而是讓它們去競爭鎖,只有獲得鎖的線程才能進入就緒狀態。
public class NotifyTest {

    public synchronized void work() {
        System.out.println("Begin Work");
        try {
            //等待喚醒
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Work End");
    }

    public synchronized void continueWork() {
        notifyAll();
    }

    public static void main(String[] args) throws InterruptedException {
        NotifyTest test = new NotifyTest();
        new Thread(() -> test.work()).start();

        //等待3000毫秒后喚醒,繼續工作。
        Thread.sleep(3000);
        test.continueWork();
    }
}

注意:wait()notify()notifyAll() 的實現依賴于monitor,所以這些方法必須運行在被synchronized關鍵字修飾的方法或代碼塊中(因為使用synchronized關鍵字可以獲得monitor,詳見上一篇文章),否則在運行時會拋出java.lang.IllegalMonitorStateException異常,你可以嘗試去掉上面代碼中的synchronized關鍵字,再執行代碼試試。

  • 生產者-消費者并發模型

有一群生產者進程在生產產品,并將這些產品提供給消費者進程去消費。為使生產者進程與消費者進程能并發執行,在兩者之間設置了一個具有n個緩沖區的緩沖池,生產者進程將它所生產的產品放入一個緩沖區中;消費者進程可從一個緩沖區中取走產品去消費。盡管所有的生產者進程和消費者進程都是以異步方式運行的,但它們之間必須保持同步,即不允許消費者進程到一個空緩沖區去取產品,也不允許生產者進程向一個已裝滿產品且尚未被取走的緩沖區投放產品。

生產者-消費者并發模型是并發編程中一個經典的模型,最早由Dijkstra提出,用以演示它提出的信號量機制。在并發編程中使用該模型能夠解決絕大多數并發問題。該模式通過平衡生產線程和消費線程的工作能力來提高程序的整體處理數據的速度。可以通過wait()notifyAll()來實現該模型。

public class ProducerConsumerModelWaitNotifyImpl {
    public static void main(String[] args) {
        List<Product> buffer = new LinkedList<Product>();
        ExecutorService es = Executors.newFixedThreadPool(5);
        //兩個生產者
        es.execute(new Producer(buffer));
        es.execute(new Producer(buffer));
        //三個消費者
        Consumer consumer = new Consumer(buffer);
        es.execute(new Consumer(buffer));
        es.execute(new Consumer(buffer));
        es.execute(new Consumer(buffer));

        es.shutdown();
        /**
         輸出:
         ...
         生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
         生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
         消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
         消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
         消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
         消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
         消費者[pool-1-thread-5]消費了一個產品:iPhone 手機
         生產者[pool-1-thread-1]生產了一個產品:iPhone 手機
         消費者[pool-1-thread-4]消費了一個產品:iPhone 手機
         生產者[pool-1-thread-2]生產了一個產品:iPhone 手機
         ...
         */
    }

    //產品
    static class Product {

        private String name;

        public Product(String name) {
            this.name = name;
        }

        @Override
        public String toString() {
            return name;
        }
    }

    //生產者
    static class Producer implements Runnable {

        private final int maxSize = 10;//產品最大庫存量

        private List<Product> buffer;

        public Producer(List<Product> buffer) {
            this.buffer = buffer;
        }

        public void run() {
            while (true) {
                synchronized (buffer) {
                    while (buffer.size() >= maxSize) {
                        try {
                            buffer.wait();//將當前線程放入等鎖(buffer對象的鎖)池,并釋放鎖。
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //模擬生產需要500毫秒
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    Product product = new Product("iPhone 手機");
                    buffer.add(product);
                    System.out.println("生產者[" + Thread.currentThread().getName() + "]生產了一個產品:" + product);
                    buffer.notifyAll();//生產完畢通知等待池內的其他線程(生產者或消費者都有可能)
                }
            }
        }
    }
    //消費者
    static class Consumer implements Runnable {

        private List<Product> buffer;

        public Consumer(List<Product> buffer) {
            this.buffer = buffer;
        }

        public void run() {
            while (true) {
                synchronized (buffer) {
                    while (buffer.isEmpty()) {
                        try {
                            buffer.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("消費者[" + Thread.currentThread().getName() + "]消費了一個產品:" + buffer.remove(0));
                    buffer.notifyAll();//消費完畢通知等待池內的其他線程(生產者或消費者都有可能)
                }
            }
        }

    }
}

查看生產者-消費者模型源碼

  • sleep(long millis)
    使一個正在運行的線程處于“睡眠狀態”,sleep()方法只是暫時讓出CPU的執行權,并不會釋放監視器鎖,調用此方法要處理java.lang.InterruptedException異常。關于該方法還有個段子代碼,如下。
public class Interceptor { 

  void  after(){
        //讓所有接口都慢一點,產品經理下次讓你優化性能的時候你減一點睡眠時間就OK了。。。
         Thread.sleep(100);
      }

  }
  • sleep(long millis, int nanos)
    如上,時間單位精確到納秒。
  • yield()
    使一個正在運行的線程暫停執行(讓出CPU)讓其他線程執行,即回到就緒狀態。
public class YieldTest {
    public  void work(){
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " Working");
            Thread.yield();
        }
    }
    public static void main(String[] args) {
        YieldTest test = new YieldTest();
        new Thread(() -> test.work()).start();
        new Thread(() -> test.work()).start();
        /**
         輸出:
             Thread-0 Working
             Thread-1 Working
             Thread-0 Working
             Thread-1 Working
             Thread-0 Working
             Thread-1 Working
         **/
    }
}
  • join()
    使主線程等待子線程執行完成后再執行,換句話說就是將線程的并行執行變為串行執行。
public class JoinTest {

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> System.out.println(Thread.currentThread().getName() + " Work End"));
        thread1.start();
        thread1.join();//合并到主線程,主線程將等待該子線程執行完畢才會執行

        Thread thread2 = new Thread(() -> System.out.println(Thread.currentThread().getName() + " Work End"));
        thread2.start();
        thread1.join();//合并到主線程,主線程將等待該子線程執行完畢才會執行

        System.out.println("Main Thread Work End");
        /**
         輸出:
             Thread-0 Work End
             Thread-1 Work End
             Main Thread Work End

         不使用join():
             Main Thread Work End
             Thread-0 Work End
             Thread-1 Work End
         */
    }
}
  • join(long millis)
    如上,但是主線程只會等待一定的時間(毫秒),如果時間到了該子線程仍未執行完,則放棄等待,執行主線程自己的代碼。
public class TimedJoinTest{

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            try {
                //模擬子線程需要執行500毫秒
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " Work End");
        });
        thread.start();
        thread.join(100);//合并到主線程,主線程將等待該子線程執行完畢才會執行,只等待100毫秒,過時不在等。

        System.out.println("Main Thread Work End");

        /**
         輸出:
             Main Thread Work End
             Thread-0 Work End
         
         刪除Thread.sleep(500);或者降到100以內:
             Thread-0 Work End
             Main Thread Work End
         */
    }
}

參考


查看《淺析Java并發編程》系列文章目錄

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,119評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,382評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,038評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,853評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,616評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,112評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,192評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,355評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,869評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,727評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,928評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,467評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,165評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,570評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,813評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,585評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,892評論 2 372

推薦閱讀更多精彩內容