java并發(fā)之線程協(xié)作

在多線程開發(fā)中貌矿,常常在線程間進(jìn)行切換或調(diào)度虎敦,那么就會(huì)出現(xiàn)線程協(xié)作滔岳。線程協(xié)作有幾種方式如下:

  1. 阻塞/喚醒
  2. 讓步
  3. 取消(中斷)

先上一個(gè)簡(jiǎn)圖吧

阻塞/喚醒

  • suspend()/resume()(已過時(shí),建議不用

    在早期java中采用suspend()驻仅、resume()對(duì)線程進(jìn)行阻塞與喚醒,但這種方式產(chǎn)生死鎖的風(fēng)險(xiǎn)很大登渣,因?yàn)榫€程被掛起以后不會(huì)釋放鎖噪服,可能與其他線程、主線程產(chǎn)生死鎖胜茧。

  • wait()/notify()

    wait粘优、notify形式通過一個(gè)object作為信號(hào),wait和notify是Object的實(shí)例方法竹揍,和線程安全敬飒、線程協(xié)作都有關(guān)系邪铲,線程安全指wait和notify方法只能在synchronized臨界區(qū)內(nèi)調(diào)用芬位,wait使當(dāng)前線程釋放cpu和鎖進(jìn)入等待阻塞,notify/notifyAll喚醒因調(diào)用當(dāng)前對(duì)象wait方法進(jìn)入等待阻塞的線程带到。notify()準(zhǔn)許阻塞的一個(gè)線程通過昧碉,notifyAll()允許所有線程通過。

      /**
       * @Author 安仔夏天勤奮
       * Create Date is  2019/3/29
       * Des  Java程序演示如何在線程中使用notify()和notifyAll()
       * notify()和notifyAll()如何通知線程揽惹,哪個(gè)線程被喚醒等被饿。
       */
      public class TestDiff {
    
      private volatile boolean isWait = false;
      private final static TestDiff test = new TestDiff();
    
      public static void main(String args[]) throws InterruptedException {
    
          WaitThread waitTask = new WaitThread();
          NotifyThread notifyTask = new NotifyThread();
    
          Thread t1 = new Thread(waitTask, "wait_1"); 
          Thread t2 = new Thread(waitTask, "wait_2"); 
          Thread t3 = new Thread(waitTask, "wait_3");
          Thread t4 = new Thread(notifyTask,"notify_1");
    
          t1.start();
          t2.start();
          t3.start();
    
          Thread.sleep(200);
          t4.start();
    
      }
    
      //線程等待
       static class WaitThread implements Runnable{
          @Override
          public void run() {
              try {
                  test.shouldWait();
              } catch (InterruptedException ex) {
                  System.out.println(Thread.currentThread() + " 等待 異常 InterruptedException");
              }
              System.out.println(Thread.currentThread() + " 等待 finished Execution");
          }
      }
    
      //通知喚醒
      static  class NotifyThread implements Runnable{
          @Override
          public void run() {
              test.wokeUp();
              System.out.println(Thread.currentThread() + " 喚醒 finished Execution");
          }
      }
    
      /*
       * wait、notify只能synchronized方法或鎖中調(diào)用
       */
      private synchronized void shouldWait() throws InterruptedException {
          while(isWait != true){
              System.out.println(Thread.currentThread()+ " 正在等待Object對(duì)象");
              wait(); //釋放鎖和獲取喚醒
              System.out.println(Thread.currentThread() + " 被喚醒");
          }
          isWait = false;
      }
    
      /**
       * wait搪搏、notify只能synchronized方法或鎖中調(diào)用
       * 線程都被鎖定在“this”關(guān)鍵字引用的當(dāng)前對(duì)象上 狭握,notify()或notifyAll()喚醒線程
       */
      private synchronized void wokeUp() {
          while (isWait == false){
              System.out.println(Thread.currentThread()+ " 將通知所有或一個(gè)線程等待這個(gè)對(duì)象");
              isWait = true; //使等待線程的條件為真
              notify(); //等待線程wait_1, wait_2,wait_3只能有一個(gè)被喚醒
      //            notifyAll(); // 等待線程wait_1, wait_2,wait_3 都被喚醒
              }
          }
      }
    

    結(jié)果打印

    調(diào)用notify()的打印結(jié)果
    Thread[wait_1,5,main] 正在等待Object對(duì)象
    Thread[wait_2,5,main] 正在等待Object對(duì)象
    Thread[wait_3,5,main] 正在等待Object對(duì)象
    Thread[notify_1,5,main] 將通知所有或一個(gè)線程等待這個(gè)對(duì)象
    Thread[wait_1,5,main] 被喚醒
    Thread[notify_1,5,main] 喚醒 finished Execution
    Thread[wait_1,5,main] 等待 finished Execution
    
    調(diào)用notifyAll()的打印結(jié)果
    Thread[wait_1,5,main] 正在等待Object對(duì)象
    Thread[wait_2,5,main] 正在等待Object對(duì)象
    Thread[wait_3,5,main] 正在等待Object對(duì)象
    Thread[notify_1,5,main] 將通知所有或一個(gè)線程等待這個(gè)對(duì)象
    Thread[wait_3,5,main] 被喚醒
    Thread[wait_2,5,main] 被喚醒
    Thread[wait_3,5,main] 等待 finished Execution
    Thread[wait_2,5,main] 正在等待Object對(duì)象
    Thread[wait_1,5,main] 被喚醒
    Thread[wait_1,5,main] 正在等待Object對(duì)象
    Thread[notify_1,5,main] 喚醒 finished Execution
    

    notify()與notifyAll()這兩個(gè)方法區(qū)別
    Java提供了兩個(gè)方法notify和notifyAll來喚醒在某些條件下等待的線程,你可以使用它們中的任何一個(gè)疯溺,但是Java中的notify和notifyAll之間存在細(xì)微差別论颅,從上面的例子打印出的結(jié)果更能他們兩者的差別。這使得它成為Java中流行的多線程面試問題之一囱嫩。當(dāng)你調(diào)用notify時(shí)恃疯,只有一個(gè)等待線程會(huì)被喚醒而且它不能保證哪個(gè)線程會(huì)被喚醒,這取決于線程調(diào)度器墨闲。雖然如果你調(diào)用notifyAll方法今妄,那么等待該鎖的所有線程都會(huì)被喚醒,但是在執(zhí)行剩余的代碼之前鸳碧,所有被喚醒的線程都將爭(zhēng)奪鎖定盾鳞,這就是為什么在循環(huán)上調(diào)用wait,因?yàn)槿绻鄠€(gè)線程被喚醒瞻离,那么線程是將獲得鎖定將首先執(zhí)行腾仅,它可能會(huì)重置等待條件,這將迫使后續(xù)線程等待琐脏。因此攒砖,notify和notifyAll之間的關(guān)鍵區(qū)別在于notify()只會(huì)喚醒一個(gè)線程缸兔,而notifyAll方法將喚醒所有線程。

  • await()/singal()

    Condition類提供吹艇,而Condition對(duì)象由new ReentLock().newCondition()獲得惰蜜,與wait和notify相同,因?yàn)槭褂肔ock鎖后無法使用wait方法受神。

  • park()/unpark()

    LockSupport提供的park和unpark方法抛猖,提供避免死鎖和競(jìng)態(tài)條件,很好地代替suspend和resume組合鼻听。park與unpark方法控制的顆粒度更加細(xì)小财著,能準(zhǔn)確決定線程在某個(gè)點(diǎn)停止,進(jìn)而避免死鎖的產(chǎn)生撑碴。

      /**
       * @Author 安仔夏天勤奮
       * Create Date is  2019/3/29
       */
       public class TestThread {
        public static void main(String []arg){
       
          XThread xThread = new XThread();
          xThread.setName("AN");
          xThread.start();
          try {
              Thread.sleep(10);
              xThread.park();
              Thread.sleep(10000);
              xThread.unPark();
              Thread.sleep(10000);
              xThread.park();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
      }
      private static class XThread extends Thread{
          private boolean isPark = false;
          @Override
          public void run() {
              System.out.println("我正在運(yùn)行撑教。。醉拓。");
              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組合真正解耦了線程之間的同步伟姐,不再需要另外的對(duì)象變量存儲(chǔ)狀態(tài),并且也不需要考慮同步鎖亿卤。

讓步 join()/yield()

  • join:線程聯(lián)合

    JDK文檔的定義:public final void join()throws InterruptedException: Waits for this thread to die.

    join為Thread的實(shí)例方法愤兵,用于線程聯(lián)合,在線程thread1中調(diào)用線程thread2.join()方法排吴,thread1會(huì)讓出CPU使用進(jìn)入阻塞直到thread2線程執(zhí)行結(jié)束秆乳;如果調(diào)用thread2.join()指定時(shí)間參數(shù),則表示thread1的最多等待時(shí)間钻哩。也可以這樣融解:t.join()方法阻塞調(diào)用此方法的線程(calling thread)屹堰,直到線程t完成,此線程再繼續(xù)憋槐;通常用于在main()主線程內(nèi)双藕,等待其它線程完成再結(jié)束main()主線程。

    下面輸出不調(diào)用join()的一段代碼

      /**
       * @Author 安仔夏天勤奮
       * Create Date is  2019/3/29
       */
    public static void main(String[] args){
      System.out.println("MainThread run start.");
      //啟動(dòng)一個(gè)子線程
      Thread threadA = new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("threadA run start.");
              try {
                  Thread.sleep(1000);
              } catch (Exception e) {
                  e.printStackTrace();
              }
              System.out.println("threadA run finished.");
          }
      });
      threadA.start();
      System.out.println("MainThread join before.");
      System.out.println("MainThread run finished.");
    }
    

    打倒輸出結(jié)果

    MainThread run start. 
    threadA run start. 
    MainThread join before .
    MainThread run finished. 
    threadA run finished.
    

    下面輸出調(diào)用join()的一段代碼

    /**
       * @Author 安仔夏天勤奮
       * Create Date is  2019/3/29
       */
    public static void main(String[] args){
      System.out.println("MainThread run start.");
      //啟動(dòng)一個(gè)子線程
      Thread threadA = new Thread(new Runnable() {
          @Override
          public void run() {
              System.out.println("threadA run start.");
              try {
                  Thread.sleep(1000);
              } catch (Exception e) {
                  e.printStackTrace();
              }
              System.out.println("threadA run finished.");
          }
      });
      threadA.start();
      System.out.println("MainThread join before.");
      try {
          threadA.join();    //調(diào)用join()
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
      System.out.println("MainThread run finished.");
    }
    

    打倒輸出結(jié)果

    MainThread run start. 
    threadA run start. 
    MainThread join before. 
    threadA run finished. 
    MainThread run finished.
    

    注意:如果join和synchronized同時(shí)使用時(shí)需要注意阳仔,線程thread1中調(diào)用thread2.join方法忧陪,thread1只讓出CPU使用但不釋放鎖,這很容易死鎖近范。

  • yield:線程切換
    yield為Thread靜態(tài)方法嘶摊,線程放棄繼續(xù)執(zhí)行,讓出cpu釋放鎖進(jìn)入可運(yùn)行狀態(tài)评矩。理論上叶堆,yield意味著放手,放棄斥杜,投降虱颗。一個(gè)調(diào)用yield()方法的線程告訴虛擬機(jī)它樂意讓其他線程占用自己的位置沥匈。這表明該線程沒有在做一些緊急的事情。注意忘渔,這僅是一個(gè)暗示高帖,并不能保證不會(huì)產(chǎn)生任何影響。

    Thread.java中yield()源碼定義

    /**
      * A hint to the scheduler that the current thread is willing to yield its current use of a processor. The 
    scheduler is free to ignore
      * this hint. Yield is a heuristic attempt to improve relative progression between threads that would 
    otherwise over-utilize a CPU.
      * Its use should be combined with detailed profiling and benchmarking to ensure that it actually has the 
    desired effect.
      */
    public static native void yield();
    

    隨意的創(chuàng)建了名為生產(chǎn)者和消費(fèi)者的兩個(gè)線程畦粮。生產(chǎn)者設(shè)定為最小優(yōu)先級(jí)散址,消費(fèi)者設(shè)定為最高優(yōu)先級(jí)。在Thread.yield()注釋和非注釋的情況下我將分別運(yùn)行該程序宣赔。沒有調(diào)用yield()方法時(shí)预麸,雖然輸出有時(shí)改變,但是通常消費(fèi)者行先打印出來儒将,然后事生產(chǎn)者吏祸。
    上代碼

      /**
       * @Author 安仔夏天勤奮
       * Create Date is  2019/3/29
       */
    public class TestYieldExample{
     public static void main(String[] args){
        Thread producer = new Producer();
        Thread consumer = new Consumer();
        producer.setPriority(Thread.MIN_PRIORITY); //Min Priority
        consumer.setPriority(Thread.MAX_PRIORITY); //Max Priority
        producer.start();
        consumer.start();
     }
    }
    
    class Producer extends Thread{
     public void run(){
        for (int i = 0; i < 5; i++){
           System.out.println("I am Producer : Produced Item " + i);
           Thread.yield();
        }
     }
    }
    
    class Consumer extends Thread{
     public void run(){
        for (int i = 0; i < 5; i++){
           System.out.println("I am Consumer : Consumed Item " + i);
           Thread.yield();
        }
     }
    }
    

    打倒輸出結(jié)果

    沒有調(diào)用yield()方法情況下的輸出
     I am Consumer : Consumed Item 0
     I am Consumer : Consumed Item 1
     I am Consumer : Consumed Item 2
     I am Consumer : Consumed Item 3
     I am Consumer : Consumed Item 4
     I am Producer : Produced Item 0
     I am Producer : Produced Item 1
     I am Producer : Produced Item 2
     I am Producer : Produced Item 3
     I am Producer : Produced Item 4
    
    在調(diào)用yield()方法情況下的輸出
     I am Producer : Produced Item 0
     I am Consumer : Consumed Item 0
     I am Producer : Produced Item 1
     I am Consumer : Consumed Item 1
     I am Producer : Produced Item 2
     I am Consumer : Consumed Item 2
     I am Producer : Produced Item 3
     I am Consumer : Consumed Item 3
     I am Producer : Produced Item 4
     I am Consumer : Consumed Item 4
    

    注意:執(zhí)行yield只是讓線程暫停一下,讓系統(tǒng)重新調(diào)度椅棺,大多數(shù)情況犁罩,yield后系統(tǒng)會(huì)繼續(xù)選擇當(dāng)前線程執(zhí)行(所以不好驗(yàn)證)

取消(中斷)

每個(gè)線程都有一個(gè)標(biāo)志位齐蔽,標(biāo)志當(dāng)前線程是否中斷两疚,Thread類中有獲取當(dāng)前線程中斷狀態(tài)及設(shè)置當(dāng)前線程為中斷狀態(tài)的方法:

  • interrupted:Thread的類方法,獲取當(dāng)前線程的中斷狀態(tài)含滴,并重置當(dāng)前線程為非中斷狀態(tài)诱渤。
  • isInterrupted:Thread的實(shí)例方法,獲取當(dāng)前線程的中斷狀態(tài)谈况。
  • interrupt:Thread的實(shí)例方法勺美,設(shè)置當(dāng)前線程為中斷狀態(tài);(只是單純的設(shè)置線程的中斷標(biāo)志碑韵,至于線程中斷后做什么在線程驅(qū)動(dòng)的任務(wù)中可以通過捕獲異成娜祝或獲取中斷狀態(tài)后自己定義)。
 /**
   * @Author 安仔夏天勤奮
   * Create Date is  2019/3/29
   */
public class TestInterrupt{
  public class InterruptRunnableDemo extends Thread {
    @Override
    //這種設(shè)計(jì)比較好,當(dāng)調(diào)用阻塞操作時(shí),會(huì)因?yàn)閽伋霎惓M顺?當(dāng)不調(diào)用阻塞操作時(shí),會(huì)因?yàn)闄z查中斷狀態(tài)而退出
    public void run(){
        try{
            while(!Thread.interrupted()){
               // 循環(huán)代碼              
            }
            System.out.println("Exit normal");
        }catch(Exception e){
            System.out.println("interrupted");
        }
    }
  public static void main(String[] args) throws InterruptedException {
            Thread thread = new InterruptRunnableDemo();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
  }
}

注意

  1. 如果線程因wait/sleep/join方法進(jìn)入等待阻塞祝闻,或因調(diào)用Lock對(duì)象的lockInterrupity/tryLock(time)進(jìn)入同步阻塞狀態(tài)占卧,其他線程中調(diào)用interrupt方法會(huì)導(dǎo)致阻塞線程中拋出InterruptException異常;
  2. 阻塞狀態(tài)的線程拋出InterruptException時(shí)會(huì)重置中斷標(biāo)志(標(biāo)志位false)联喘;
  3. 類方法interrupted獲取中斷狀態(tài)后會(huì)清除中斷狀態(tài)华蜒,實(shí)例方法isInterrupted()只是獲取中斷狀態(tài);
  4. 除使用interrupt方法中斷線程外豁遭,還有2種方式中止線程執(zhí)行叭喜,a. 退出標(biāo)志使線程正常退出(線程通信);b.使用stop()方法強(qiáng)行終止線程(不推薦蓖谢,可能發(fā)生不可預(yù)料的結(jié)果)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末捂蕴,一起剝皮案震驚了整個(gè)濱河市譬涡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌啥辨,老刑警劉巖昂儒,帶你破解...
    沈念sama閱讀 221,695評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異委可,居然都是意外死亡渊跋,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,569評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門着倾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來拾酝,“玉大人,你說我怎么就攤上這事卡者≥锒冢” “怎么了?”我有些...
    開封第一講書人閱讀 168,130評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵崇决,是天一觀的道長(zhǎng)材诽。 經(jīng)常有香客問我,道長(zhǎng)恒傻,這世上最難降的妖魔是什么脸侥? 我笑而不...
    開封第一講書人閱讀 59,648評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮盈厘,結(jié)果婚禮上睁枕,老公的妹妹穿的比我還像新娘。我一直安慰自己沸手,他們只是感情好外遇,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,655評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著契吉,像睡著了一般跳仿。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捐晶,一...
    開封第一講書人閱讀 52,268評(píng)論 1 309
  • 那天菲语,我揣著相機(jī)與錄音,去河邊找鬼租悄。 笑死谨究,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的泣棋。 我是一名探鬼主播胶哲,決...
    沈念sama閱讀 40,835評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼潭辈!你這毒婦竟也來了鸯屿?” 一聲冷哼從身側(cè)響起澈吨,我...
    開封第一講書人閱讀 39,740評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寄摆,沒想到半個(gè)月后谅辣,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,286評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡婶恼,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,375評(píng)論 3 340
  • 正文 我和宋清朗相戀三年桑阶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勾邦。...
    茶點(diǎn)故事閱讀 40,505評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蚣录,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出眷篇,到底是詐尸還是另有隱情萎河,我是刑警寧澤,帶...
    沈念sama閱讀 36,185評(píng)論 5 350
  • 正文 年R本政府宣布蕉饼,位于F島的核電站虐杯,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏昧港。R本人自食惡果不足惜擎椰,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,873評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望慨飘。 院中可真熱鬧确憨,春花似錦、人聲如沸瓤的。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,357評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽圈膏。三九已至,卻和暖如春篙骡,著一層夾襖步出監(jiān)牢的瞬間稽坤,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,466評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工糯俗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留尿褪,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,921評(píng)論 3 376
  • 正文 我出身青樓得湘,卻偏偏與公主長(zhǎng)得像杖玲,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淘正,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,515評(píng)論 2 359