1. 程式人生 > >Java多執行緒通關——基礎知識挑戰

Java多執行緒通關——基礎知識挑戰

等掌握了基礎知識之後,才有資格說基礎知識沒用這樣的話。否則就老老實實的開始吧。

 

 

物件的監視器


每一個Java物件都有一個監視器。並且規定,每個物件的監視器每次只能被一個執行緒擁有,只有擁有它的執行緒把它釋放之後,這個監視器才會被其它執行緒擁有。

其實就是說,物件的監視器對於多執行緒來說是互斥的,即一個執行緒從拿到它之後到釋放它之前這段時間內,其它執行緒是絕對不可能再拿到它的。這是由JVM保證的。

這樣一來,物件的監視器就可以用來保護那種每次只允許一個執行緒執行的方法或程式碼片段,就是我們常說的同步方法或同步程式碼塊。

Java包括兩種範疇的物件(當然,這樣講可能不準確,主要用於幫助理解),一種就是普通的物件,比如new Object()。一種就是描述型別資訊的物件,即Class<?>型別的物件。


這兩類都是Java物件,這毋庸置疑,所以它們都有監視器。但這兩類物件又有明顯的不同,所以它們的監視器對外表現的行為也是不同的。

請看下面表示式:

 

Object o1 = new Object();Object o2 = new Object();o1 == o2; //false

 

o1和o2是分別new出來的兩個物件,它們肯定不相同。又因為監視器是和物件關聯的,所以o1的監視器和o2的監視器也是不同的,且它們沒有任何關係。

所以必須是同一個物件的監視器才行,不同物件的監視器達不到預期的效果,這一點要切記。

再看下面的表示式:

 

o1.getClass() == o2.getClass(); //true
o1.getClass() == Object.class; //true

 

但是o1的型別資訊物件(o1.getClass())和o2的型別資訊物件(o2.getClass())是同一個,且和Object類的型別資訊物件(Object.class)也是同一個。這不廢話嘛,o1和o2都是從Object類new出來的。哈哈。

型別資訊物件本身的型別是Class<?>,在類載入器(ClassLoader)載入一個類後,就會生成一個和該類相關的Class<?>型別的物件,該物件會被快取起來,所以型別資訊物件是全域性(同一個JVM同一個類載入器)唯一的。

這也就說明了,為什麼同一個類new出來的多個物件是不同的,但是它們的型別資訊物件卻是同一個,且可以使用“類.class”直接獲取到它。


Java語言規定,使用synchronized關鍵字可以獲取物件的監視器。下面分別來看這兩類物件的監視器用法。

普遍物件的監視器:

 

class SyncA {  //方法A  public synchronized void methodA() {    //同步方法,當前物件的監視器  }  //方法B  public void methodB() {    synchronized(this) {    //同步程式碼塊,當前物件的監視器    }  }}
class SyncB { //物件 private SyncA syncA; public SyncB(SyncA syncA) { this.syncA = syncA; } //方法C public void methodC() { synchronized(syncA) { //同步程式碼塊,syncA物件的監視器 } }}
//new一個物件SyncA syncA = new SyncA();//把該物件傳進去SyncB syncB = new SyncB(syncA);//A、B、C這三個方法都要擁有syncA這個物件的監視器才能執行new Thread(syncA::methodA).start();new Thread(syncA::methodB).start();new Thread(syncB::methodC).start();

 

這三個執行緒都去獲取同一個物件(即syncA)的監視器,因為一個物件的監視器一次只能被一個執行緒擁有,所以這三個執行緒是逐次獲取到的,因此這三個方法也是逐次執行的。

這個示例告訴我們,利用物件的監視器可以做到的,並不只是同一個方法不能同時被多個執行緒執行,多個不同的方法也可以不能同時被多個執行緒執行,只要它們用到的是同一個物件的監視器。

型別資訊物件的監視器:

 

class SyncC {  //靜態方法A  public static synchronized void methodA() {    //同步方法,型別資訊物件的監視器  }  //靜態方法B  public static void methodB() {    synchronized(SyncC.class) {    //同步程式碼塊,型別資訊物件的監視器    }  }}
class SyncD { //型別資訊物件 private Class<SyncC> syncClass; public SyncD(Class<SyncC> syncClass) { this.syncClass = syncClass; } //方法C public void methodC() { synchronized(syncClass) { //同步程式碼塊,SyncC類的型別資訊物件的監視器 } } //方法D public void methodD() { synchronized(syncClass) { //同步程式碼塊,SyncC類的型別資訊物件的監視器 } }}
//A、B、C、D這四個方法都要擁有SyncC類的型別資訊物件的監視器才能執行new Thread(SyncC::methodA).start();new Thread(SyncC::methodB).start();new Thread(new SyncD(SyncC.class)::methodC).start();new Thread(new SyncD((Class<SyncC>)new SyncC().getClass())::methodD).start();

 

因為一個類的型別資訊物件只有一個,所以這四個執行緒其實是在競爭同一個物件的監視器,因此這四個方法也是逐次執行的。

通過這個示例,再次強調一下,不管是方法還是程式碼塊,不管是靜態的還是例項的,也不管是屬於同一個類的還是多個類的,只要它們共用同一個物件的監視器,那麼這些方法或程式碼塊在多執行緒下是無法併發執行的,只能逐個執行,因為同一個物件的監視器每次只能被一個執行緒所擁有,其它執行緒此時只能被阻塞著。

注:在實際使用中,一定要確保是同一個物件,尤其是使用字串型別或數字型別的物件時,一定要注意。


幾個重要的方法


首先是Object類的wait/notify/notifyAll方法,因為Java中的所有類最終都繼承自Object類,所以,可以使用任何Java物件來呼叫這三個方法。

不過Java規定,要在某個物件上呼叫這三個方法,必須先獲取那個物件的監視器才行。再次提醒,監視器是和物件關聯的,不同的物件監視器也是不同的。

請看下面的用法:

 

//new一個物件Object obj = new Object();//獲取物件的監視器synchronized(obj) {  //在物件上呼叫wait方法  obj.wait();}

 

很多人首次接觸這一部分的時候一般都會比較懵,主要是因為搞不清人物關係。

這裡的wait方法雖然是在物件(即obj)上呼叫的,但卻不是讓這個物件等待的。而是讓執行這行程式碼(即obj.wati())的執行緒(即Thread)在這個物件(即obj)上等待的。

這裡的執行緒是等待的“主體”,物件是等待的“位置”。比如學校開運動會時,會在操場上為每班劃定一個位置,並插上一個牌子,寫上班級名稱。

這個牌子就相當於物件obj,它表示一個位置資訊。當學生看到本班牌子之後,就會自動去牌子後面排隊等待。

學生就相當於執行緒,當學生看到牌子就相當於當執行緒執行到obj.wait(),學生去牌子後面排隊等待就相當於執行緒在物件obj上等待。

當執行緒執行完obj.wait()後,就會釋放掉物件obj的監視器,轉而進入物件obj的等待集合中進行等待,執行緒由執行狀態變為等待(WAITING)狀態。此後這個執行緒將不再被執行緒排程器排程。

(說明一點,當多個執行緒去競爭同一個物件的監視器而沒有競爭上時,執行緒會變為阻塞(BLOCKED)狀態,而非等待狀態。)

執行緒選擇等待的原因大多都是因為需要的資源暫時得不到,那什麼時候資源能就位讓執行緒再次執行呢?其實是不太好確定的,那乾脆就到資源OK時通知它一聲吧。

請看下面的方法:

 

//獲取物件(還是上面那個)的監視器synchronized(obj) {  //在物件上呼叫notify方法  obj.notify();}

 

有了上面的基礎,現在就好理解多了。程式碼的意思就是通知在物件obj上等待的執行緒,把其中一個喚醒。即把這個執行緒從物件obj的等待集合中移除。此後這個執行緒就又可以被執行緒排程器排程了。可能有一部分人覺得現在被喚醒的那個執行緒就可以執行了,其實不然。

當前執行緒執行完notify方法後,必須要釋放掉物件obj的監視器,這樣被喚醒的那個執行緒才能重新獲取物件obj的監視器,這樣才可以繼續執行。

就是當一個執行緒想要通過wait進入等待時,需要獲取物件的監視器。當別的執行緒通過notify喚醒這個執行緒時,這個執行緒想要繼續執行,還需要獲取物件的監視器。

notifyAll方法的用法和notify是一樣的,只是含義不同,表示通知物件obj上所有等待的執行緒,把它們全部都喚醒。雖然是全部喚醒,但也只能有一個執行緒可以執行,因為每次只有一個執行緒能獲取到物件obj的監視器。

還有一種wait方法是帶有超時時間的,它表示執行緒進入等待的時間達到超時時間後還沒有被喚醒時,它會自動醒來(也可以認為是被系統喚醒的)。

這種情況下沒有超時異常丟擲,雖然執行緒是自動醒來,但想要繼續執行的話,同樣需要先獲取物件obj的監視器才行。

注:執行緒通過wait進入等待時,只會釋放和這個wait相關的那個物件的監視器。如果此時執行緒還擁有其它物件的監視器,並不會去釋放它們,而是在等待期間一直擁有。這塊一定要注意,避免死鎖。

使用須知:

處在等待狀態的執行緒,可能會被意外喚醒,即此時條件並不滿足,但是卻被喚醒了。當然,這種情況在實踐中很少發生。但是我們還是要做一些措施來應對,那就是再次檢測條件是否滿足,不滿足的話再次進入等待。

可見這是一個具有重複性的邏輯,因此把它放到一個迴圈裡是最合適的,如下這樣:

 

//獲取物件的監視器synchronized(obj) {  //判斷條件是否滿足  while(condition is not satisfied) {    //在物件上呼叫wait方法    obj.wait();
            
           

相關推薦

Java執行通關——基礎知識挑戰

等掌握了基礎知識之後,才有資格說基礎知識沒用這樣的話。否則就老老實實的開始吧。     物件的監視器每一個Java物件都有一個監視器。並且規定,每個物件的監視器每次只能被一個執行緒擁有,只有擁有它的執行緒把它釋放之後,這個監視器才會被其它執行緒擁有。其實就是說,物件的監視器對於多執行

Android小知識-Java執行基礎知識瞭解下

本平臺的文章更新會有延遲,大家可以關注微信公眾號-顧林海,包括年底前會更新kotlin由淺入深系列教程,目前計劃在微信公眾號進行首發,如果大家想獲取最新教程,請關注微信公眾號,謝謝! 十月份離職,在家修養一個多月,這一個多月做了很多事,自己的微信公眾號開通了,部落格也換了一種風格,在簡書和掘金分享

Java執行一些基礎知識

最近複習了一些多執行緒方面的基礎知識,做一下總結,多以自己的理解來文字敘述,如果有漏點或者理解錯的地方,歡迎各位大佬多多指出; ps:執行緒分為使用者執行緒和守護執行緒,當程式中的所有的使用者執行緒都執行完了之後,JVM就退出執行了,下面所講的都是使用者執行緒為例,我們一般建立一個新執行緒物件,預設都是使用者

Java執行程式設計基礎知識彙總

## 多執行緒簡介 ### 多工   現代作業系統(Windows、Linux、MacOS)都可以執行多工,多工就是同時執行多個任務。例如在我們的計算機上,一般都同時跑著多個程式,例如瀏覽器,視訊播放器,音樂播放器,Word辦公軟體等等,由於CPU執行程式碼都是一條一條順序執行的,即時是單

Java執行系列---“基礎篇”14之 wait,sleep,join,yield,park,unpark,notify等通訊機制對比

1. 執行緒讓步: yield() yield()的作用是讓步。它能讓當前執行緒由“執行狀態”進入到“就緒狀態”,從而讓其它具有相同優先順序的等待執行緒獲取執行權;但是,並不能保證在當前執行緒呼叫yield()之後,其它具有相同優先順序的執行緒就一定能獲得執行權;也有可能是當前執行緒又進入到“執行狀態”繼續

java:執行基礎(引入)

* 1.什麼是執行緒     * 執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒     * 多執行緒併發執行可以提高程式的效率, 可以同時完成多項工作* 2.多執行緒的應用場景     * 紅

Java執行基礎篇(二)

上一篇介紹了Java多執行緒的基礎概念和synchronized關鍵字,這篇繼續介紹Java多執行緒的其他關鍵字和重要的方法。 一、volatile關鍵字 1.1 Java記憶體模型

Java執行基礎篇(一)

一、併發和並行 1.1 概念 1.2 比較 1.3 程序和執行緒 二、基礎概念 2.1 執

執行基礎知識

多執行緒 程式 程式是含有指令和資料的檔案,被儲存在磁碟或者其他的資料儲存裝置中,程式是靜態的程式碼。 程序 程序是程式的一次執行過程,是系統執行程式的基本單位,程序是動態的。 多工 多工是指在一個程式中可以同時執行多個程序,即有多個獨立執行的任務,每一個任務對

java執行高併發知識總結

1.      計算機系統 使用快取記憶體來作為記憶體與處理器之間的緩衝,將運算需要用到的資料複製到快取中,讓計算能快速進行;當運算結束後再從快取同步回記憶體之中,這樣處理器就無需等待緩慢的記憶體讀寫了。 快取一致性:多處理器系統中,因為共享同一主記憶體,

JAVA執行基礎部分

public void transfer(int from, int to, double amount){synchronized(lockObj){ // if (energyBoxes[from] < amount) // return; //while迴圈,保證條件不滿足時任務都會被條件阻擋 /

java執行程式設計基礎講解

講解java多執行緒的問題之前,我們需要了解兩個概念: 1.程序:可以先簡單理解為,就是我們開發的完成某種部分功能的程式程式碼在CPU中跑起來之後的樣子。所以程序應該是作業系統分配的記憶體空間+1個或多個執行緒組成的。 2.執行緒:可以理解為組成程序的一些程式碼流,這些程式

Java執行程式設計總結筆記——02執行基礎知識

讀解Thread類API 構造方法摘要 Thread(Runnable target) 分配新的 Thread 物件。 Thread(String name) 分配新的 Thread 物件。 方法摘要 static Thread cur

Java執行核心技術(一):基礎知識總結

概念 程序:程序是作業系統結構的基礎,是一次程式的執行,是一個程式及其資料在處理機上順序執行時所發生的活動,是程式在一個程式集合上執行的過程,它是系統進行資源分配和排程的一個基本單位。 執行緒:執行緒是程序中獨立執行的子任務。使用多執行緒技術後,可以在同一時間執行更多不同種

Java執行-基礎及實現

1. 什麼是執行緒 執行緒是程序內的執行單元     某個程序當中都有若干個執行緒。 執行緒是程序內的執行單元。 使用執行緒的原因是,程序的切換是非常重量級

Java執行(一)基礎

1.關於執行緒與程序的區別:執行緒指程序中的一個執行場景,也就是執行流程,同一個程序中的執行緒共享其程序中的記憶體和資源(共享的記憶體是堆記憶體和方法區記憶體,棧記憶體不共享,每個執行緒有自己的,一個執行緒一個棧。);每個程序是一個應用程式,都有獨立的記憶體空間。多執行緒的使用是為了提高程式

java 執行基礎2

      多執行緒(加入執行緒join) thread.join(); 被.join()的執行緒優先執行 多執行緒(執行緒優先順序Priority) Thread.setPriority(1-10)執行緒預設優先順序是5。執行緒優先順序的範圍是

java執行 基礎

引入  1.什麼是執行緒           執行緒是程式執行的一條路徑, 一個程序中可以包含多條執行緒        

java執行知識(1)

基礎概念 1.執行緒和程序: 程序有自己的獨立空間,而執行緒共享程序的空間 執行緒通訊方便,同一程序的執行緒共享全域性變數,靜態資料 多程序更健壯,多執行緒只要有一個執行緒死掉,整個程序也死 2.同步和非同步:同步必須等該方法的呼叫返回 3.並行和

java基礎總結(三十二)--java執行程式設計例項

來自:https://blog.csdn.net/qq_34996727/article/details/80416277或者https://www.cnblogs.com/pureEve/p/6524366.html 一.相關知識:   Java多執行緒程式設計到的知識: