国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
Java 并發(fā)編程:線程間的協(xié)作(wait/notify/sleep/yield/join)

Java并發(fā)編程系列:

一、線程的狀態(tài)

   Java中線程中狀態(tài)可分為五種:New(新建狀態(tài)),Runnable(就緒狀態(tài)),Running(運行狀態(tài)),Blocked(阻塞狀態(tài)),Dead(死亡狀態(tài))。

  New:新建狀態(tài),當線程創(chuàng)建完成時為新建狀態(tài),即new Thread(...),還沒有調(diào)用start方法時,線程處于新建狀態(tài)。

  Runnable:就緒狀態(tài),當調(diào)用線程的的start方法后,線程進入就緒狀態(tài),等待CPU資源。處于就緒狀態(tài)的線程由Java運行時系統(tǒng)的線程調(diào)度程序(thread scheduler)來調(diào)度。

  Running:運行狀態(tài),就緒狀態(tài)的線程獲取到CPU執(zhí)行權(quán)以后進入運行狀態(tài),開始執(zhí)行run方法。

  Blocked:阻塞狀態(tài),線程沒有執(zhí)行完,由于某種原因(如,I/O操作等)讓出CPU執(zhí)行權(quán),自身進入阻塞狀態(tài)。

  Dead:死亡狀態(tài),線程執(zhí)行完成或者執(zhí)行過程中出現(xiàn)異常,線程就會進入死亡狀態(tài)。

  這五種狀態(tài)之間的轉(zhuǎn)換關(guān)系如下圖所示:

 

 

  有了對這五種狀態(tài)的基本了解,現(xiàn)在我們來看看Java中是如何實現(xiàn)這幾種狀態(tài)的轉(zhuǎn)換的。 

二、wait/notify/notifyAll方法的使用

  1、wait方法:

void wait()Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
void wait(long timeout)Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
void wait(long timeout, int nanos)Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object, or some other thread interrupts the current thread, or a certain amount of real time has elapsed.

  JDK中一共提供了這三個版本的方法,

 ?。?)wait()方法的作用是將當前運行的線程掛起(即讓其進入阻塞狀態(tài)),直到notify或notifyAll方法來喚醒線程.

  (2)wait(long timeout),該方法與wait()方法類似,唯一的區(qū)別就是在指定時間內(nèi),如果沒有notify或notifAll方法的喚醒,也會自動喚醒。

  (3)至于wait(long timeout,long nanos),本意在于更精確的控制調(diào)度時間,不過從目前版本來看,該方法貌似沒有完整的實現(xiàn)該功能,其源碼(JDK1.8)如下:

 1 public final void wait(long timeout, int nanos) throws InterruptedException { 2         if (timeout < 0) { 3             throw new IllegalArgumentException("timeout value is negative"); 4         } 5  6         if (nanos < 0 || nanos > 999999) { 7             throw new IllegalArgumentException( 8                                 "nanosecond timeout value out of range"); 9         }10 11         if (nanos >= 500000 || (nanos != 0 && timeout == 0)) {12             timeout++;13         }14 15         wait(timeout);16     }

  從源碼來看,JDK8中對納秒的處理,只做了四舍五入,所以還是按照毫秒來處理的,可能在未來的某個時間點會用到納秒級別的精度。雖然JDK提供了這三個版本,其實最后都是調(diào)用wait(long timeout)方法來實現(xiàn)的,wait()方法與wait(0)等效,而wait(long timeout,int nanos)從上面的源碼可以看到也是通過wait(long timeout)來完成的。下面我們通過一個簡單的例子來演示wait()方法的使用:

 1 package com.paddx.test.concurrent; 2  3 public class WaitTest { 4  5     public void testWait(){ 6         System.out.println("Start-----"); 7         try { 8             wait(1000); 9         } catch (InterruptedException e) {10             e.printStackTrace();11         }12         System.out.println("End-------");13     }14 15     public static void main(String[] args) {16         final WaitTest test = new WaitTest();17         new Thread(new Runnable() {18             @Override19             public void run() {20                 test.testWait();21             }22         }).start();23     }24 }

  這段代碼的意圖很簡單,就是程序執(zhí)行以后,讓其暫停一秒,然后再執(zhí)行。運行上述代碼,查看結(jié)果:

Start-----
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
    at java.lang.Object.wait(Native Method)
    at com.paddx.test.concurrent.WaitTest.testWait(WaitTest.java:8)
    at com.paddx.test.concurrent.WaitTest$1.run(WaitTest.java:20)
    at java.lang.Thread.run(Thread.java:745)

  這段程序并沒有按我們的預期輸出相應結(jié)果,而是拋出了一個異常。大家可能會覺得奇怪為什么會拋出異常?而拋出的IllegalMonitorStateException異常又是什么?我們可以看一下JDK中對IllegalMonitorStateException的描述:

Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.

  這句話的意思大概就是:線程試圖等待對象的監(jiān)視器或者試圖通知其他正在等待對象監(jiān)視器的線程,但本身沒有對應的監(jiān)視器的所有權(quán)。其實這個問題在《Java并發(fā)編程:Synchronized及其實現(xiàn)原理》一文中有提到過,wait方法是一個本地方法,其底層是通過一個叫做監(jiān)視器鎖的對象來完成的。所以上面之所以會拋出異常,是因為在調(diào)用wait方式時沒有獲取到monitor對象的所有權(quán),那如何獲取monitor對象所有權(quán)?Java中只能通過Synchronized關(guān)鍵字來完成,修改上述代碼,增加Synchronized關(guān)鍵字:

 1 package com.paddx.test.concurrent; 2  3 public class WaitTest { 4  5     public synchronized void testWait(){//增加Synchronized關(guān)鍵字 6         System.out.println("Start-----"); 7         try { 8             wait(1000); 9         } catch (InterruptedException e) {10             e.printStackTrace();11         }12         System.out.println("End-------");13     }14 15     public static void main(String[] args) {16         final WaitTest test = new WaitTest();17         new Thread(new Runnable() {18             @Override19             public void run() {20                 test.testWait();21             }22         }).start();23     }24 }

  現(xiàn)在再運行上述代碼,就能看到預期的效果了:

Start-----
End-------

  所以,通過這個例子,大家應該很清楚,wait方法的使用必須在同步的范圍內(nèi),否則就會拋出IllegalMonitorStateException異常,wait方法的作用就是阻塞當前線程等待notify/notifyAll方法的喚醒,或等待超時后自動喚醒。

2、notify/notifyAll方法

void notify()Wakes up a single thread that is waiting on this object's monitor.
void notifyAll()Wakes up all threads that are waiting on this object's monitor.

  有了對wait方法原理的理解,notify方法和notifyAll方法就很容易理解了。既然wait方式是通過對象的monitor對象來實現(xiàn)的,所以只要在同一對象上去調(diào)用notify/notifyAll方法,就可以喚醒對應對象monitor上等待的線程了。notify和notifyAll的區(qū)別在于前者只能喚醒monitor上的一個線程,對其他線程沒有影響,而notifyAll則喚醒所有的線程,看下面的例子很容易理解這兩者的差別:

 1 package com.paddx.test.concurrent; 2  3 public class NotifyTest { 4     public synchronized void testWait(){ 5         System.out.println(Thread.currentThread().getName() +" Start-----"); 6         try { 7             wait(0); 8         } catch (InterruptedException e) { 9             e.printStackTrace();10         }11         System.out.println(Thread.currentThread().getName() +" End-------");12     }13 14     public static void main(String[] args) throws InterruptedException {15         final NotifyTest test = new NotifyTest();16         for(int i=0;i<5;i++) {17             new Thread(new Runnable() {18                 @Override19                 public void run() {20                     test.testWait();21                 }22             }).start();23         }24 25         synchronized (test) {26             test.notify();27         }28         Thread.sleep(3000);29         System.out.println("-----------分割線-------------");30         31         synchronized (test) {32             test.notifyAll();33         }34     }35 }

輸出結(jié)果如下:

Thread-0 Start-----
Thread-1 Start-----
Thread-2 Start-----
Thread-3 Start-----
Thread-4 Start-----
Thread-0 End-------
-----------分割線-------------
Thread-4 End-------
Thread-3 End-------
Thread-2 End-------
Thread-1 End-------

  從結(jié)果可以看出:調(diào)用notify方法時只有線程Thread-0被喚醒,但是調(diào)用notifyAll時,所有的線程都被喚醒了。

  最后,有兩點點需要注意:

 ?。?)調(diào)用wait方法后,線程是會釋放對monitor對象的所有權(quán)的。

 ?。?)一個通過wait方法阻塞的線程,必須同時滿足以下兩個條件才能被真正執(zhí)行:

  •     線程需要被喚醒(超時喚醒或調(diào)用notify/notifyll)。
  •     線程喚醒后需要競爭到鎖(monitor)。

三、sleep/yield/join方法解析

   上面我們已經(jīng)清楚了wait和notify方法的使用和原理,現(xiàn)在我們再來看另外一組線程間協(xié)作的方法。這組方法跟上面方法的最明顯區(qū)別是:這幾個方法都位于Thread類中,而上面三個方法都位于Object類中。至于為什么,大家可以先思考一下?,F(xiàn)在我們逐個分析sleep/yield/join方法:

  1、sleep

  sleep方法的作用是讓當前線程暫停指定的時間(毫秒),sleep方法是最簡單的方法,在上述的例子中也用到過,比較容易理解。唯一需要注意的是其與wait方法的區(qū)別。最簡單的區(qū)別是,wait方法依賴于同步,而sleep方法可以直接調(diào)用。而更深層次的區(qū)別在于sleep方法只是暫時讓出CPU的執(zhí)行權(quán),并不釋放鎖。而wait方法則需要釋放鎖。

 1 package com.paddx.test.concurrent; 2  3 public class SleepTest { 4     public synchronized void sleepMethod(){ 5         System.out.println("Sleep start-----"); 6         try { 7             Thread.sleep(1000); 8         } catch (InterruptedException e) { 9             e.printStackTrace();10         }11         System.out.println("Sleep end-----");12     }13 14     public synchronized void waitMethod(){15         System.out.println("Wait start-----");16         synchronized (this){17             try {18                 wait(1000);19             } catch (InterruptedException e) {20                 e.printStackTrace();21             }22         }23         System.out.println("Wait end-----");24     }25 26     public static void main(String[] args) {27         final SleepTest test1 = new SleepTest();28 29         for(int i = 0;i<3;i++){30             new Thread(new Runnable() {31                 @Override32                 public void run() {33                     test1.sleepMethod();34                 }35             }).start();36         }37 38 39         try {40             Thread.sleep(10000);//暫停十秒,等上面程序執(zhí)行完成41         } catch (InterruptedException e) {42             e.printStackTrace();43         }44         System.out.println("-----分割線-----");45 46         final SleepTest test2 = new SleepTest();47 48         for(int i = 0;i<3;i++){49             new Thread(new Runnable() {50                 @Override51                 public void run() {52                     test2.waitMethod();53                 }54             }).start();55         }56 57     }58 }

 執(zhí)行結(jié)果:

Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
Sleep start-----
Sleep end-----
-----分割線-----
Wait start-----
Wait start-----
Wait start-----
Wait end-----
Wait end-----
Wait end-----

  這個結(jié)果的區(qū)別很明顯,通過sleep方法實現(xiàn)的暫停,程序是順序進入同步塊的,只有當上一個線程執(zhí)行完成的時候,下一個線程才能進入同步方法,sleep暫停期間一直持有monitor對象鎖,其他線程是不能進入的。而wait方法則不同,當調(diào)用wait方法后,當前線程會釋放持有的monitor對象鎖,因此,其他線程還可以進入到同步方法,線程被喚醒后,需要競爭鎖,獲取到鎖之后再繼續(xù)執(zhí)行。

2、yield方法
  yield方法的作用是暫停當前線程,以便其他線程有機會執(zhí)行,不過不能指定暫停的時間,并且也不能保證當前線程馬上停止。yield方法只是將Running狀態(tài)轉(zhuǎn)變?yōu)镽unnable狀態(tài)。我們還是通過一個例子來演示其使用:

 1 package com.paddx.test.concurrent; 2  3 public class YieldTest implements Runnable { 4     @Override 5     public void run() { 6         try { 7             Thread.sleep(100); 8         } catch (InterruptedException e) { 9             e.printStackTrace();10         }11         for(int i=0;i<5;i++){12             System.out.println(Thread.currentThread().getName() + ": " + i);13             Thread.yield();14         }15     }16 17     public static void main(String[] args) {18         YieldTest runn = new YieldTest();19         Thread t1 = new Thread(runn,"FirstThread");20         Thread t2 = new Thread(runn,"SecondThread");21 22         t1.start();23         t2.start();24 25     }26 }

運行結(jié)果如下:

FirstThread: 0
SecondThread: 0
FirstThread: 1
SecondThread: 1
FirstThread: 2
SecondThread: 2
FirstThread: 3
SecondThread: 3
FirstThread: 4
SecondThread: 4

  這個例子就是通過yield方法來實現(xiàn)兩個線程的交替執(zhí)行。不過請注意:這種交替并不一定能得到保證,源碼中也對這個問題進行說明:

/**
     * 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.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
*/

這段話主要說明了三個問題:

  •   調(diào)度器可能會忽略該方法。
  •   使用的時候要仔細分析和測試,確保能達到預期的效果。
  •   很少有場景要用到該方法,主要使用的地方是調(diào)試和測試?! ?/li>

3、join方法

void join()Waits for this thread to die.
void join(long millis)Waits at most millis milliseconds for this thread to die.
void join(long millis, int nanos)Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.

  join方法的作用是父線程等待子線程執(zhí)行完成后再執(zhí)行,換句話說就是將異步執(zhí)行的線程合并為同步的線程。JDK中提供三個版本的join方法,其實現(xiàn)與wait方法類似,join()方法實際上執(zhí)行的join(0),而join(long millis, int nanos)也與wait(long millis, int nanos)的實現(xiàn)方式一致,暫時對納秒的支持也是不完整的。我們可以看下join方法的源碼,這樣更容易理解:

 1 public final void join() throws InterruptedException { 2         join(0); 3     } 4  5  public final synchronized void join(long millis) 6     throws InterruptedException { 7         long base = System.currentTimeMillis(); 8         long now = 0; 9 10         if (millis < 0) {11             throw new IllegalArgumentException("timeout value is negative");12         }13 14         if (millis == 0) {15             while (isAlive()) {16                 wait(0);17             }18         } else {19             while (isAlive()) {20                 long delay = millis - now;21                 if (delay <= 0) {22                     break;23                 }24                 wait(delay);25                 now = System.currentTimeMillis() - base;26             }27         }28     }29 30 public final synchronized void join(long millis, int nanos)31     throws InterruptedException {32 33         if (millis < 0) {34             throw new IllegalArgumentException("timeout value is negative");35         }36 37         if (nanos < 0 || nanos > 999999) {38             throw new IllegalArgumentException(39                                 "nanosecond timeout value out of range");40         }41 42         if (nanos >= 500000 || (nanos != 0 && millis == 0)) {43             millis++;44         }45 46         join(millis);47     }

  大家重點關(guān)注一下join(long millis)方法的實現(xiàn),可以看出join方法就是通過wait方法來將線程的阻塞,如果join的線程還在執(zhí)行,則將當前線程阻塞起來,直到join的線程執(zhí)行完成,當前線程才能執(zhí)行。不過有一點需要注意,這里的join只調(diào)用了wait方法,卻沒有對應的notify方法,原因是Thread的start方法中做了相應的處理,所以當join的線程執(zhí)行完成以后,會自動喚醒主線程繼續(xù)往下執(zhí)行。下面我們通過一個例子來演示join方法的作用:

(1)不使用join方法:

 1 package com.paddx.test.concurrent; 2  3 public class JoinTest implements Runnable{ 4     @Override 5     public void run() { 6  7         try { 8             System.out.println(Thread.currentThread().getName() + " start-----"); 9             Thread.sleep(1000);10             System.out.println(Thread.currentThread().getName() + " end------");11         } catch (InterruptedException e) {12             e.printStackTrace();13         }14     }15 16     public static void main(String[] args) {17         for (int i=0;i<5;i++) {18             Thread test = new Thread(new JoinTest());19             test.start();20         }21 22         System.out.println("Finished~~~");23     }24 }

執(zhí)行結(jié)果如下:

Thread-0 start-----
Thread-1 start-----
Thread-2 start-----
Thread-3 start-----
Finished~~~
Thread-4 start-----
Thread-2 end------
Thread-4 end------
Thread-1 end------
Thread-0 end------
Thread-3 end------

(2)使用join方法:

 1 package com.paddx.test.concurrent; 2  3 public class JoinTest implements Runnable{ 4     @Override 5     public void run() { 6  7         try { 8             System.out.println(Thread.currentThread().getName() + " start-----"); 9             Thread.sleep(1000);10             System.out.println(Thread.currentThread().getName() + " end------");11         } catch (InterruptedException e) {12             e.printStackTrace();13         }14     }15 16     public static void main(String[] args) {17         for (int i=0;i<5;i++) {18             Thread test = new Thread(new JoinTest());19             test.start();20             try {21                 test.join(); //調(diào)用join方法22             } catch (InterruptedException e) {23                 e.printStackTrace();24             }25         }26 27         System.out.println("Finished~~~");28     }29 }

執(zhí)行結(jié)果如下:

Thread-0 start-----
Thread-0 end------
Thread-1 start-----
Thread-1 end------
Thread-2 start-----
Thread-2 end------
Thread-3 start-----
Thread-3 end------
Thread-4 start-----
Thread-4 end------
Finished~~~

 對比兩段代碼的執(zhí)行結(jié)果很容易發(fā)現(xiàn),在沒有使用join方法之間,線程是并發(fā)執(zhí)行的,而使用join方法后,所有線程是順序執(zhí)行的。

四、總結(jié)

  本文主要詳細講解了wait/notify/notifyAll和sleep/yield/join方法。最后回答一下上面提出的問題:wait/notify/notifyAll方法的作用是實現(xiàn)線程間的協(xié)作,那為什么這三個方法不是位于Thread類中,而是位于Object類中?位于Object中,也就相當于所有類都包含這三個方法(因為Java中所有的類都繼承自Object類)。要回答這個問題,還是得回過來看wait方法的實現(xiàn)原理,大家需要明白的是,wait等待的到底是什么東西?如果對上面內(nèi)容理解的比較好的話,我相信大家應該很容易知道wait等待其實是對象monitor,由于Java中的每一個對象都有一個內(nèi)置的monitor對象,自然所有的類都理應有wait/notify方法。

本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊舉報
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
java線程概述 -- JR 精品文章
Object類wait,notify,notifyAll的使用
Java并發(fā)編程:3-Thread類的使用
sleep()和wait()有什么區(qū)別
Java中的Object的Wait() 和notify()方法使用時應注意的地方和Thread中的sleep()方法
java線程的幾個概念和方法
更多類似文章 >>
生活服務
分享 收藏 導長圖 關(guān)注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服