Java多執行緒同步中同步程式碼塊、wait、notify與notifyAll的真正含義與工作原理
今天在和導師討論Java多執行緒程式設計的同步問題時,發現自己對同步程式碼塊、wait()方法、notify()方法和notifyAll()方法的理解不太清晰,於是在網上查閱資料,可是結果眾說紛紜,又在導師的啟發和指導下結合程式設計驗證後得出如下結論。
Java中的每一個物件例項都有一個鎖標記和鎖池,鎖標記預設允許進入。當一個執行緒嘗試進入以該物件為鎖的同步程式碼塊時,JVM會執行獲取鎖的操作,該操作首先檢視鎖標記是否為允許進入:
- 如果允許進入,則JVM將該標記改為不允許進入後,允許該執行緒進入該同步程式碼塊執行。
- 如果不允許進入,則JVM不改動該標記(保持為不允許進入狀態),同時儲存該執行緒的上下文環境後將其休眠,並加入鎖池中。
當允許進入該同步程式碼塊中執行的執行緒退出該同步程式碼塊時,JVM執行釋放鎖的操作,該操作會檢查鎖池中是否存在休眠的執行緒:
- 如果鎖池不為空,則JVM選取一個鎖池中的執行緒將其移出,併為其恢復上下文環境,喚醒並讓其參與執行緒排程。(因為休眠前剛好進入要進入同步程式碼塊,所以當其獲取到CPU執行許可權後就開始執行同步程式碼塊中的程式碼了)
- 如果鎖池為空,則JVM將該同步程式碼塊的鎖標記置為允許進入即可。
當在同步程式碼塊中執行wait()操作時,JVM的執行流程如下:
- 對該物件執行釋放鎖的操作(見2)。
- 儲存本執行緒的上下文環境,將本執行緒加入該物件的等待池中,然後休眠不再參與CPU排程。
當同步程式碼塊中的程式碼執行notify方法時,JVM會從目標物件的等待池中選出一個執行緒並將其移到鎖池中;而如果執行的是notifyAll方法,則將等待池中的所有執行緒一併加入鎖池中。需要注意的是,無論notify方法還是notifyAll方法,都只是在等待池與鎖池之間進行移動操作,並不會喚醒任何執行緒。只有當前執行緒退出同步程式碼塊後,JVM才會從鎖池中選出一個執行緒讓其繼續執行。(由於進入等待池的執行緒在休眠前儲存了上下文環境,所以其在繼續執行後是從wait()之中恢復並執行的,保證了執行結果的一致性)
下面通過一段程式碼對以上所述進行驗證:
import java.util.concurrent.TimeUnit; public
這段程式碼的執行結果如下:
[email protected]70dea4e Thread0 trying to enter syn block. Thread1 trying to enter syn block. Thread0 entered the syn block.Now sleep 2s... Thread2 trying to enter syn block. Thread3 trying to enter syn block. Thread4 trying to enter syn block. Thread0 returm from sleep, begin to invoke wait()... Thread4 entered the syn block.Now sleep 2s... Thread4 returm from sleep, begin to invoke wait()... Thread3 entered the syn block.Now sleep 2s... Thread3 returm from sleep, begin to invoke wait()... Thread2 entered the syn block.Now sleep 2s... Thread2 returm from sleep, begin to invoke wait()... Thread1 entered the syn block.Now sleep 2s... Thread1 returm from sleep, begin to invoke wait()... Main Thread entered syn block...begin to invoke notifyAll() Main Thread return from notifyAll()...begin to sleep Main Thread quit from syn block. Thread1 returm from wait() Thread1 is in loop for 3 time Thread1 is in loop for 2 time Thread1 is in loop for 1 time Thread2 returm from wait() Thread2 is in loop for 3 time Thread2 is in loop for 2 time Thread2 is in loop for 1 time Thread3 returm from wait() Thread3 is in loop for 3 time Thread3 is in loop for 2 time Thread3 is in loop for 1 time Thread4 returm from wait() Thread4 is in loop for 3 time Thread4 is in loop for 2 time Thread4 is in loop for 1 time Thread0 returm from wait() Thread0 is in loop for 3 time Thread0 is in loop for 2 time Thread0 is in loop for 1 time
從中可以看出以下幾點:
- 同步程式碼塊在同一時間只允許一個執行緒進入
- 在同步程式碼塊中呼叫wait()方法會執行釋放鎖的操作
- 呼叫notifyAll方法後,處於wait()狀態的執行緒們並沒有被立刻喚醒,而是等當前執行緒退出同步程式碼塊後才順序執行
- 如果該notifyAll方法為notify方法,則最終只有一個執行緒能從從wait方法中返回。
- 最後補充一點,wait、notify和notifyAll方法是任何Object及其派生類物件都有的方法,其呼叫範圍僅限於同步方法和同步程式碼塊中,在外部直接呼叫會拋java.lang.IllegalMonitorStateException執行時異常。
所以,之前將Java的多執行緒同步與Windows的多執行緒同步直接進行類比,特別是按照語義直接想當然地理解wait和notify、notifyAll()等方法得出的結論是錯誤的。紙上得來終覺淺,絕知此事要躬行啊!
相關推薦
Java多執行緒6 中同步函式的鎖和同步程式碼塊的鎖的區別
同步程式碼塊的出現是解決了多執行緒的安全問題,但是它增加了程式碼的縮排層級,同時降低了效率(每次無論是不是對的鎖,每個路徑都要去判斷) 針對同步出現的這兩個問題,首先討論第一個。因此引出一個新的知識點———————— 同步函式 關於同步函式的使用(一買車票的程式碼為例子
Java多執行緒程式設計中執行緒的同步與互斥/執行緒安全/Java鎖
摘要:多執行緒三個特徵:原子性、可見性以及有序性.>執行緒的同步與互斥?(同步執行緒與非同步執行緒,執行緒同步和非同步問題) 1.同步:假設現有執行緒A和執行緒B,執行緒A需要往緩衝區寫資料,執行緒B需要從緩衝區讀資料,但他們之間存在一種制約
java 多執行緒synchronized鎖同步方法,同步程式碼塊
執行緒安全問題 同步和非同步 我們知道多個執行緒共享堆記憶體,當兩個或者多個執行緒呼叫同一個物件的方法操作物件成員時,因為cpu輪流執行執行緒,執行緒A剛開始操作物件方法,修改了資料,輪到執行緒B執行,執行緒B也操作物件方法,修改資料,可能又輪到執行緒A操作物件方法,接著上次執行緒A的剩餘部
java多執行緒10.構建同步工具
建立狀態依賴類的最簡單方法通常是在類庫中現有狀態依賴類的基礎上進行構造。如果類庫中沒有提供你需要的功能,可以使用java語言和類庫提供的底層機制來構造自己的同步機制,包括內建的條件佇列、顯示地Condition物件以及AbstractQueuedSynchronizer框架。 在單執行緒程式中呼叫方法時,如
java多執行緒程式設計之使用Synchronized塊同步變數
通過synchronized塊來同步特定的靜態或非靜態方法。 要想實現這種需求必須為這些特性的方法定義一個類變數,然後將這些方法的程式碼用synchronized塊括起來,並將這個類變數作為引數傳入synchronized塊 下面的程式碼演示瞭如何同步特定的類方法:
java多執行緒--簡易使用同步鎖實現一對一交替列印
一、本例需要分析的地方不多,只需要使用一個同步鎖+一個計數器就能搞定,直接奉送原始碼吧: package com.example.liuxiaobing.statemodel.mutil_thr
Java多執行緒10:同步不具有繼承性
父類的同步操作子類是不可以繼承獲得的 package unit2; public class Demo8_tongbubujujichengxing { public static void m
thrift java多執行緒非阻塞同步/非同步呼叫例項
作者:呂桂強 郵箱:[email protected] 首先建立thrift檔案 namespace java thriftservice Hello{ string helloString(1:string para)} 執行thrift -ge
Java多執行緒程式設計中Future模式的詳解
Java多執行緒程式設計中,常用的多執行緒設計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產者-消費者模式等。這篇文章主要講述Future模式,關於其他多執行緒設計模式的地址如下: 關於其他多執行緒設
Java多執行緒程式設計中生產者-消費者模式的詳解
生產者-消費者模式是一個經典的多執行緒設計模式,它為多執行緒的協作提供了良好的解決方案。在生產者-消費者模式中,通常有兩類執行緒,即若干個生產者執行緒和若干個消費者執行緒。生產者執行緒負責提交使用者請求,消費者執行緒負責處理使用者請求。生產者和消費者之間通過共享記憶體緩衝區
Java多執行緒程式設計中Master-Worker模式的詳解
Java多執行緒程式設計中,常用的多執行緒設計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產者-消費者模式等。這篇文章主要講述Master-Worker模式,關於其他多執行緒設計模式的地址如下: 關於
Java多執行緒--併發中集合的使用之ConcurrentSkipListMap
概述 基於跳錶實現的ConcurrentNavigableMap。 1)containsKey、get、put、remove等操作的平均時間複雜度為log(n);size非固定時間操作,因非同步特性,需要遍歷所有節點才能確定size,且可能不是
Java多執行緒程式設計中不變模式的詳解
Java多執行緒程式設計中,常用的多執行緒設計模式包括:Future模式、Master-Worker模式、Guarded Suspeionsion模式、不變模式和生產者-消費者模式等。這篇文章主要講述不變模式,關於其他多執行緒設計模式的地址如下:關於Future模式的詳解:
三、JAVA多執行緒:Thread API詳細介紹 (sleep、Interrupt、Join、TimeUnit、yield、interrupted、執行緒優先順序 )
本章深入分析了Thread的所有API,熟練掌握Thread的API是學好Thread的前提。 執行緒sleep sleep是一個靜態方法,一共有兩個過載方法,一個需要傳入毫秒數,另一個既要傳入毫秒數又要傳入納秒數。 sleep方法介紹
java多執行緒的6種實現方式詳解、執行緒池、定時器
多執行緒的形式上實現方式主要有兩種,一種是繼承Thread類,一種是實現Runnable介面。本質上實現方式都是來實現執行緒任務,然後啟動執行緒執行執行緒任務(這裡的執行緒任務實際上就是run方法)。這裡所說的6種,實際上都是在以上兩種的基礎上的一些變形。 繼承Thread
Java多執行緒同步中同步程式碼塊、wait、notify與notifyAll的真正含義與工作原理
今天在和導師討論Java多執行緒程式設計的同步問題時,發現自己對同步程式碼塊、wait()方法、notify()方法和notifyAll()方法的理解不太清晰,於是在網上查閱資料,可是結果眾說紛紜,又在
-1-5 java 多執行緒 概念 程序 執行緒區別聯絡 java建立執行緒方式 執行緒組 執行緒池概念 執行緒安全 同步 同步程式碼塊 Lock鎖 sleep()和wait()方法的區別 為什麼wait(),notify(),notifyAll()等方法都定義在O
本文關鍵詞: java 多執行緒 概念 程序 執行緒區別聯絡 java建立執行緒方式 執行緒組 執行緒池概念 執行緒安全 同步 同步程式碼塊 Lock鎖 sleep()和wait()方法的區別 為什麼wait(),notify(),notifyAll()等方法都定義在Object類中 多執行緒
Java多執行緒同步---以銀行存取錢的過程的簡單程式碼例項
首先存錢取錢的這個操作,應該是執行緒操作的,可以有很多的顧客,這意思就是得有多個執行緒,多個執行緒之間共同操作一個銀行,銀行的金額就需要同步。才能保證執行緒安全。 所以,下面就把這個程式碼的例項放 這,有不對的地方,還請指出來哈。因為有個老鐵問這個多執行緒的程式碼。 首先是
Java多執行緒程式設計-(12)-Java中的佇列同步器AQS和ReentrantLock鎖原理簡要分析
原文出自 : https://blog.csdn.net/xlgen157387/article/details/78341626 一、Lock介面 在上一篇文章中: Java多執行緒程式設計-(5)-使用Lock物件實現同步以及執行緒間通訊 介紹
java多執行緒解決同步問題的幾種方式、原理和程式碼
生產者類: publicclassProducerextendsThread{// 每次生產的產品數量privateint num;// 所在放置的倉庫privateStorage storage;// 建構函式,設定倉庫publicProducer(Storage storage){this.stora