Java 併發程式設計:執行緒間的協作(wait/notify/sleep/yield/join) (r)
Java併發程式設計系列:
一、執行緒的狀態
Java中執行緒中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(執行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。
New:新建狀態,當執行緒建立完成時為新建狀態,即new Thread(...),還沒有呼叫start方法時,執行緒處於新建狀態。
Runnable:就緒狀態,當呼叫執行緒的的start方法後,執行緒進入就緒狀態,等待CPU資源。處於就緒狀態的執行緒由Java執行時系統的執行緒排程程式(thread scheduler)來排程。
Running:執行狀態,就緒狀態的執行緒獲取到CPU執行權以後進入執行狀態,開始執行run方法。
Blocked:阻塞狀態,執行緒沒有執行完,由於某種原因(如,I/O操作等)讓出CPU執行權,自身進入阻塞狀態。
Dead:死亡狀態,執行緒執行完成或者執行過程中出現異常,執行緒就會進入死亡狀態。
這五種狀態之間的轉換關係如下圖所示:
有了對這五種狀態的基本瞭解,現在我們來看看Java中是如何實現這幾種狀態的轉換的。
二、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中一共提供了這三個版本的方法,
(1)wait()方法的作用是將當前執行的執行緒掛起(即讓其進入阻塞狀態),直到notify或notifyAll方法來喚醒執行緒.
(2)wait(long timeout),該方法與wait()方法類似,唯一的區別就是在指定時間內,如果沒有notify或notifAll方法的喚醒,也會自動喚醒。
(3)至於wait(long timeout,long nanos),本意在於更精確的控制排程時間,不過從目前版本來看,該方法貌似沒有完整的實現該功能,其原始碼(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提供了這三個版本,其實最後都是呼叫wait(long timeout)方法來實現的,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 @Override 19 public void run() { 20 test.testWait(); 21 } 22 }).start(); 23 } 24 }
這段程式碼的意圖很簡單,就是程式執行以後,讓其暫停一秒,然後再執行。執行上述程式碼,檢視結果:
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 )
|
這段程式並沒有按我們的預期輸出相應結果,而是丟擲了一個異常。大家可能會覺得奇怪為什麼會丟擲異常?而丟擲的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.
|
這句話的意思大概就是:執行緒試圖等待物件的監視器或者試圖通知其他正在等待物件監視器的執行緒,但本身沒有對應的監視器的所有權。其實這個問題在《Java併發程式設計:Synchronized及其實現原理》一文中有提到過,wait方法是一個本地方法,其底層是通過一個叫做監視器鎖的物件來完成的。所以上面之所以會丟擲異常,是因為在呼叫wait方式時沒有獲取到monitor物件的所有權,那如何獲取monitor物件所有權?Java中只能通過Synchronized關鍵字來完成,修改上述程式碼,增加Synchronized關鍵字:
1 package com.paddx.test.concurrent; 2 3 public class WaitTest { 4 5 public synchronized void testWait(){//增加Synchronized關鍵字 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 @Override 19 public void run() { 20 test.testWait(); 21 } 22 }).start(); 23 } 24 }
現在再執行上述程式碼,就能看到預期的效果了:
Start-----
End-------
|
所以,通過這個例子,大家應該很清楚,wait方法的使用必須在同步的範圍內,否則就會丟擲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物件來實現的,所以只要在同一物件上去呼叫notify/notifyAll方法,就可以喚醒對應物件monitor上等待的執行緒了。notify和notifyAll的區別在於前者只能喚醒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();相關推薦
Java 併發程式設計:執行緒間的協作(wait/notify/sleep/yield/join) (r)
Java併發程式設計系列: 一、執行緒的狀態 Java中執行緒中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(執行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。 New:新建狀態,當執行緒建立完成時為新建狀態,即new T
17-Java併發程式設計:執行緒間協作的兩種方式:wait、notify、notifyAll和Condition
Java併發程式設計:執行緒間協作的兩種方式:wait、notify、notifyAll和Condition 在前面我們將了很多關於同步的問題,然而在現實中,需要執行緒之間的協作。比如說最經典的生產者-消費者模型:當佇列滿時,生產者需要等待佇列有空間才能繼續往裡面放
Java併發程式設計:執行緒間協作的兩種方式:wait、notify、notifyAll和Condition
在前面我們將了很多關於同步的問題,然而在現實中,需要執行緒之間的協作。比如說最經典的生產者-消費者模型:當佇列滿時,生產者需要等待佇列有空間才能繼續往裡面放入商品,而在等待的期間內,生產者必須釋放對臨界資源(即佇列)的佔用權。因為生產者如果不釋放對臨界資源的佔用權,那麼消費者
Java 併發程式設計:執行緒間的協作(wait/notify/sleep/yield/join)
Java併發程式設計系列: 一、執行緒的狀態 Java中執行緒中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(執行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。 New:新建狀態,當執行緒建立完成時為新建狀態,即
Java併發(二十一):執行緒池實現原理 Java併發(十八):阻塞佇列BlockingQueue Java併發(十八):阻塞佇列BlockingQueue Java併發程式設計:執行緒池的使用
一、總覽 執行緒池類ThreadPoolExecutor的相關類需要先了解: (圖片來自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8%A7%88) Executor:位於最頂層,只有一個 execute(Runnab
Java併發程式設計:執行緒池的使用
如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。 那麼有沒有一種辦法使得執行緒可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務? 在J
Java併發程式設計:執行緒池的使用(轉載)
轉載自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java併發程式設計:執行緒池的使用 在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題: 如果併發的執行緒數量很多,並且每個執行緒都是執行
JAVA併發程式設計:執行緒池 ThreadPoolExecutor
生活 前期追深度,否則會華而不實,後期追廣度,否則會坐井觀天; 前言 在前面,我們已經對Thread有了比較深入的瞭解,並且已經學會了通過new Thread()來建立一個執行緒,並通過start方法來啟動一個執行緒,這種方法非常簡單,同樣也存在弊端: 1、每次通過new Thr
Java併發程式設計:執行緒安全和ThreadLocal
執行緒安全的概念:當多個執行緒訪問某一個類(物件或方法)時,這個類始終都能表現出正確的行為,那麼這個類(物件或方法)就是執行緒安全的。 執行緒安全 說的可能比較抽象,下面就以一個簡單的例子來看看什麼是執行緒安全問題。 public class MyThread
Java併發程式設計:執行緒的生命週期是個怎樣的過程?
前言 在日常開發過程中,如果我們需要執行一些比較耗時的程式的話,一般來說都是開啟一個新執行緒,把耗時的程式碼放線上程裡,然後開啟執行緒執行。但執行緒是會耗費系統資源的,如果有多個執行緒同時執行,互相之間搶佔系統資源,那無疑會對系統造成極大的壓力。所以,怎麼操作執行緒,保證不影響整個應用功能是很重要的,而這就
JAVA併發程式設計:執行緒池Executors
Java中對執行緒池提供了很好的支援,有了執行緒池,我們就不需要自已再去建立執行緒。如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。JAVA的執行緒池中的執行緒可以在執
Java併發程式設計:執行緒和鎖的使用與解析
執行緒的使用 新建執行緒 新建一個執行緒有兩種方法:繼承Thread類,然後重寫run方法;實現Runnable介面,然後實現run方法。實際上Thread類也是實現的Runnable介面,再加上類只能單繼承,所以推薦使用Runnable介面。示例如下: class Demo
Java併發程式設計:執行緒池ThreadPoolExecutor
多執行緒的程式的確能發揮多核處理器的效能。雖然與程序相比,執行緒輕量化了很多,但是其建立和關閉同樣需要花費時間。而且執行緒多了以後,也會搶佔記憶體資源。如果不對執行緒加以管理的話,是一個非常大的隱患。而執行緒池的目的就是管理執行緒。當你需要一個執行緒時,你就可以拿一個空閒執行緒去執行任務,當任務執行完後,
【轉】Java 並發編程:線程間的協作(wait/notify/sleep/yield/join)
system bre tle 都是 spec lar 調度 wait方法 plus Java中線程中狀態可分為五種:New(新建狀態),Runnable(就緒狀態),Running(運行狀態),Blocked(阻塞狀態),Dead(死亡狀態)。 New:新建
Java高併發程式設計:執行緒池
這裡首先介紹了java5中的併發的小工具包:java.util.concurrent.atomic,然後介紹了執行緒池的概念,對使用java5的方式建立不同形式的執行緒進行了演示,之後介紹了兩個 物件:Callable和Future,用於獲取執行緒執行後的結果,
JAVA執行緒間協作wait、notify、notifyAll、sleep用途
在上節中,介紹了java多執行緒中同步鎖的概念,synchronized方法和synchronized程式碼塊都是為了解決執行緒併發的問題,同一時間允許一個執行緒訪問當前類或者物件。如果涉及到執行緒間的協作通訊,就需要用到wait、notify、notifyAll方法,這三個方法是Object的
Java併發程式設計之執行緒生命週期、守護執行緒、優先順序和join、sleep、yield
Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。 Java執行緒的生命週期 Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead
Java併發程式設計(1)-執行緒安全基礎概述
文章目錄 一、執行緒安全性 1.1、無狀態類 1.2、有狀態類 二、原子性 2.1、原子操作 2.2、競爭操作 2.3、複合操作
Java併發程式設計之執行緒安全、執行緒通訊
Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程
java併發程式設計一一執行緒池原理分析(三)
合理的設定執行緒池的大小 接著上一篇探討執行緒留下的尾巴。如果合理的設定執行緒池的大小。 要想合理的配置執行緒池的大小、首先得分析任務的特性,可以從以下幾個角度分析: 1、任務的性質:CPU密集型任務、IO密集型任務、混合型任務等; 2、任務的優先順序:高、中、低; 3、任務的執行時