線程調度與單例模式:wait、notify與懶漢模式解析

一.wait 和 notify(等待 和 通知)

引入 wait notify 就是為了能夠從應用層面,干預到多個不同線程代碼的執行順序,可以讓后執行的線程主動放棄被調度的機會,等先執行的線程完成后通知放棄調度的線程重新執行。

自助取款機

當取款機沒有錢的時候,要想去取錢只能等別人進去存錢或者等銀行的人過來放錢,否則他永遠拿不到錢,他在出去之后又會進去取款,剛剛釋放了鎖,就又會參與到鎖競爭,并且大概率他會一直拿到鎖,這叫“線程餓死“,這種情況只是概率性事件,但還是會極大影響到其他線程運行,這個時候他為了不影響到其他人就會等待(wait),然后讓后面如果存錢的人存了錢了,通知(notify)自己一聲,自己就可以重新排隊取錢,在沒通知的這段時間,他就會一直在旁邊等,不會去排隊了。?

join 和 wait

join 是等待另一個線程執行完,才繼續執行
wait 則是等待另一個線程通過 notify 進行通知(不要求另一個線程必須執行完)
wait進入阻塞,只能說明自己釋放鎖了,至于是否有其他線程拿到鎖,這是不能確定的
public class ThreadDemo25 {public static void main(String[] args) {//需要有一個統一的對象進行加鎖,wait,nitifyObject locker = new Object();Thread t1 = new Thread(() -> {synchronized (locker){System.out.println("t1 wait 之前");try {locker.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(" t1 wait 之后");}});Thread t2 = new Thread(() -> {try {Thread.sleep(5000);synchronized (locker){System.out.println(" t2 notify 之前");locker.notify();System.out.println("t2 notify 之后");}} catch (InterruptedException e) {throw new RuntimeException(e);}});t1.start();t2.start();}
}

我們發現阻塞等待的原因是19行的wait

wait方法放到synchronized是因為要釋放鎖,前提是先加上鎖

java特別約定要把notify放到synchronized中?

由于線程的隨機調度我們并不知道要先調用誰,如果先調用t2就沒有線程去給t1喚醒了,所以要保證t1先執行,我們給t2加上了sleep?

需要注意的是t2在notify之后并沒有釋放鎖,而t1喚醒后會嘗試加鎖,所以會產生小小的阻塞。

notifyAll? ? ?喚醒這個對象所有等待的線程

假設有很多個線程,都使用同一個對象wait,針對這個對象進行notifyAll,此時就會全都喚醒

需要注意的是,這些線程在wait返回的時候,需要重新獲取鎖,就會因為鎖競爭,是這些線程串行執行

wait 和 sleep?

wait 提供了一個帶有超時時間的版本
sleep 也是能指定時間
都是時間到了,就繼續執行,解除阻塞了
wait 和 sleep 都可以被提前喚醒(雖然時間還沒到),wait通過notify喚醒,sleep通過interrupt喚醒
使用wait 最主要的目標一定是不知道多少時間的前提下使用的,超時時間是為了兜底
使用sleep,一定是知道了多少時間的前提下使用的,雖然能提前喚醒,但是通過異常喚醒一般是程序出現一些特殊情況了

二.單例模式

單例模式是一種設計模式,遵守設計模式下限就有了兜底

1.餓漢模式

單例 == 單個實例(對象)

static 這個引用就是我們期望創建出唯一的實例的引用,static 靜態的 指的是“類屬性”

instance 就是 當前類對象里面持有的屬性

每個類的類對象,只存在一個,類對象中的static屬性,自然也是只有一個了

?這時instance所指向的對象就是唯一的一個對象,其他代碼要想使用這個類的實例,就需要通過這個方法來進行獲取,而不是直接new一個出來。

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ???

這直接從根本上讓其他人想new都new不了了

Sington內部代碼早就把唯一的實例安排好了?

class Singleton{private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}private Singleton() {}
}
public class ThreadDemo26 {public static void main(String[] args) {Singleton s = Singleton.getInstance();}
}

上述代碼成為“餓漢模式”單例模式一種簡單的寫法,在程序啟動時,實例就創建了,所以就是用餓漢,創建實例非常早。

?2.懶漢模式

創建實例的時機不一樣了,創建實例的時機更晚,直到第一次使用的時候,才會創建實例

class SingletonLazy{//這個引用指向唯一實例,這個引用先初始化null,而不是立即創建實例private volatile static SingletonLazy instance = null;private static Object locker = new Object();public static SingletonLazy getInstance(){if(instance == null){//如果 Instance 為 NULL,就說明時首次調用,首次調用就需要考慮線程安全問題,就要加鎖//如果非空的話就說明時后續調用就不必加鎖synchronized (locker){if(instance == null) instance = new SingletonLazy();}}return instance;}private SingletonLazy(){}
}
public class ThreadDemo27 {public static void main(String[] args) {SingletonLazy s1 = SingletonLazy.getInstance();SingletonLazy s2 = SingletonLazy.getInstance();System.out.println(s1 == s2);}
}

如果是首次調用 getinstance,那么instance 引用 為null,進入if語句創建實例出來,后續再次調用返回的就已經是創建好的引用了。

餓漢模式和懶漢模式是否屬于線程安全?

?餓漢模式屬于讀操作在多線程是安全的。
但加入了懶漢模式就不一定了

?這樣就會導致實例被new 了兩次,就又bug了。這時線程不安全,在多線程可能會new出多個實例

即使寫成這樣沒有線程安全問題,但還是因為已經創建了實例,但還是進行加鎖解鎖操作使得效率降低。

?所以if不一定要加在方法上,

我們加在了實例化對象上,這樣就不會木訥的進行加鎖,但是還有一個致命的問題就是整個方法不是原子了,這個時候我們就得考慮對象是否創建問題了。

我們得先理清一下思路了,現在我們第一個if是用來判斷是不是第一次進行創建對象的,我們所面臨的問題是在多線程下if后面的代碼執行是不確定的,可能已經調用其他線程并創建了對象,所以我們得加一個判斷是否創建了對象的語句

?兩個if代碼一樣但意義不同

第一個if是為了判定是否第一次創建對象,并加鎖,第二個if是判斷是否要創建對象

在創建對象過程中還涉及到了線程安全問題 ------- 指令重排序

調整原有代碼的執行順序,保證邏輯不管的前提下,提高程序的效率

創建對象這一行代碼可以拆為三個步驟

1.申請一段內存空間
2.在這個內存上調用的方法。創建出這個實例
3.把這個內存地址賦值給instance引用變量

正常情況下是按照1,2,3順序來執行的,但是編譯器可能優化成1,3,2的順序來執行

在單線程下1,2,3或者1,3,2都是可以的

但是如果是多線程就可能引入問題了!!!

在t1加鎖之后t2進行阻塞,t1解鎖后t2獲得了鎖,但在這個時候?t2判斷不為空直接返回,但這個時候instance并未初始化,如果使用instance里面的屬性或者方法就可能會出現錯誤,那難道2不是同時進行的嗎?
注意:在執行完1,3后線程有可能也被調度走了,并未進行初始化。要想執行2可能會隔一段時間

在之前解決內存可見性時我們用到了volatile它的功能有兩個

1.保證內存可見性,每次訪問變量必須都要重新讀取內存,而不會優化到寄存器/緩存中
2.禁止指令重排序,針對這個volatile 修飾的變量的讀寫操作相關指令,是不能被重排序的

回顧一些我們解決問題的步驟

首先

我們因為餓漢模式在多線程下,會出現二次實例化對象的操作,所以我們加上了鎖,

第二次

我們因為即使加上了鎖,但我們因為加鎖的位置太消耗效率所以我們將鎖的位置改變了,但我們無法判斷對象是否被創建了,所以我們又加上了一層if,

第三次

我們了解了指令重排序,我們會遇到,對象創建但并未初始化,然后導致使用了沒初始化對象的屬性或者方法,出現了失誤,這時候我們回想起之前解決內存可見性的volatile,它的另一個功能就是解決指令重排序所以我們加上了這個關鍵字,至此我們解決了這一系列問題

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/81652.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/81652.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/81652.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

ros運行包,Ubuntu20.04成功運行LIO-SAM

zz:~/lio_sam_ws$ source devel/setup.bash zz:~/lio_sam_ws$ roslaunch lio_sam run.launch 創建包鏈接: 鏈接1:Ubuntu20.04成功運行LIO-SAM_ubuntu20.04運行liosam-CSDN博客 鏈接2:ubuntu 20.04 ROS 編譯和運行 lio-sam,并且導出PCD文件…

AI自動化工作流:開啟當下智能生產力的價值

舉手之言:AI自動化工作流創造了什么呢? AI自動化工作流 ,顧名思義,是將人工智能(AI)技術與自動化流程相結合,通過智能化的方式來完成復雜的任務和操作。簡單來說,它就是利用AI的強大…

【設計模式】- 行為型模式2

觀察者模式 定義了一對多的依賴關系,讓多個觀察者對象同時監聽某一個對象主題。這個主題對象在狀態變化時,會通知所有的觀察者對象,讓他們能夠自動更新自己。 【主要角色】 抽象主題角色:把所有觀察者對象保存在一個集合里&…

mapbox-gl強制請求需要accessToken的問題

vue引入"mapbox-gl": "^2.15.0", 1.13以后得版本,都強制需要驗證這個mapboxgl.accessToken。 解決辦法:實例化地圖的代碼中,加入這個: const originalFetch window.fetch; window.fetch function ({ url…

已知6、7、8月月平均氣溫和標準差,求夏季季平均溫度與標準差

由下面定理,得出平方和的公式:(即每天的溫度平方和) 這樣就可以推出季平均的算法: 舉例:在Excel用公式算,不要手算: 因此季平均:(B2*C2B3*C3B4*C4)/SUM(B2:B4) 季標準差…

手機內存不夠,哪些文件可以刪?

1??應用緩存文件 安卓:通過「文件管理器」→「Android」→「data」或「cache」文件夾(部分需權限),或直接在應用設置中清除緩存 iOS:無需手動清理,系統會自動管理,或在應用內設置中清除&…

可編輯98頁PPT | 某大型制造業數字化轉型戰略規劃項目方案

薦言摘要:某大型制造業數字化轉型戰略規劃項目方案聚焦企業全價值鏈升級,以“數據驅動業務重塑”為核心,打造行業標桿級數字化能力。項目將分三階段推進,首階段聚焦頂層設計,通過現狀診斷明確痛點:針對企業…

lovart design 設計類agent的系統提示詞解讀

文章目錄 lovart 設計agent介紹角色定義工作規范工具調用任務復雜度指南任務移交指南其他ref lovart 設計agent介紹 lovart作為設計agent,產品功能包括: 全鏈路設計能力:可以快速生成完整的品牌視覺方案,包括標志、配色、品牌規范…

使用 docker-volume-backup 備份 Docker 卷

docker-volume-backup 是一個用于備份 Docker 卷的工具,在 Windows 10 上使用它,你可以按照以下步驟操作: 1. 確保 Docker 環境已安裝并正常運行 在 Windows 10 上,你需要安裝 Docker Desktop for Windows。可以從 Docker 官方網…

用戶行為日志分析的常用架構

## 1. 經典Lambda架構 Lambda架構是一種流行的大數據處理架構,特別適合用戶行為日志分析場景。 ### 1.1 架構組成 Lambda架構包含三層: - **批處理層(Batch Layer)**: 存儲全量數據并進行離線批處理 - **實時處理層(Speed Layer)**: 處理最新數據&…

從API到UI:直播美顏SDK中的濾鏡與貼紙功能開發與落地方案詳解

時下,濾鏡和貼紙功能,已經成為主播們展現個性、增強互動的“必備神器”。那么,這些功能背后的技術實現到底有多復雜?如何從API到UI構建一個流暢、靈活的美顏SDK呢?本文將從底層原理到前端實現,全面解析這兩…

21.EC實戰 嵌入式控制器EC如何進入休眠模式實現低功耗

文章目錄 一、概述1. WUI0中斷向量表配置2. 中斷服務函數內容3. 深度睡眠檢測4. 深度睡眠功能函數4.1 關閉所有中斷4.2 外部中斷對應引腳功能配置4.3 設置喚醒功能和喚醒中斷4.4 進入深度睡眠狀態一、概述 EC作為筆記本電腦的嵌入式控制器,在筆記本電腦使用電池單獨工作時,關…

Java實現PDF加水印功能:技術解析與實踐指南

Java實現PDF加水印功能:技術解析與實踐指南 在當今數字化辦公環境中,PDF文件因其跨平臺兼容性和格式穩定性而被廣泛應用。然而,為了保護文檔的版權、標記文檔狀態(如“草稿”“機密”等)或增加文檔的可追溯性&#xf…

vue2、vue3項目打包生成txt文件-自動記錄打包日期:git版本、當前分支、提交人姓名、提交日期、提交描述等信息 和 前端項目的版本號json文件

vue2 打包生成text文件 和 前端項目的版本號json文件 項目打包生成txt文件-自動記錄git版本、當前分支、提交人姓名、提交日期、提交描述等信息生成版本號json文件-自動記錄當前版本號、打包時間等信息新建branch-version-webpack-plugin.js文件 // 同步子進程 const execSyn…

Filament引擎(一) ——渲染框架設計

filament是谷歌開源的一個基于物理渲染(PBR)的輕量級、高性能的實時渲染框架,其框架架構設計并不復雜,后端RHI的設計也比較簡單。重點其實在于項目中材質、光照模型背后的方程式和理論,以及對它們的實現。相關的信息,可以參考官方…

洛谷B3876—— [信息與未來 2015] 中間值

見:B3876 [信息與未來 2015] 中間值 - 洛谷 題目描述 給出一個正整數 n,生成長度為 n 的數列 a,其中 ai?i(1≤i≤n)。 若 n 為奇數,則輸出 a 的中間數(位于 a 正中位置的數);若 n 為偶數&am…

Java 后端基礎 Maven

Maven 1.什么是Maven 2.Maven的作用 Maven核心 Maven概述 IDEA集成Maven 1.創建Maven項目 點擊設置里的 Project Structure 將jdk和編譯語言進行設置 隨后點擊apply點擊ok 2.Maven坐標 3.導入Maven項目 將文件夾復制到當前項目的目錄下 在這個目錄下,在磁盤中…

qtcreater配置opencv

我配置opencv不管是按照網上的教程還是deep seek發現都有些問題,下面是我的配置方法以及實踐成功的心得 電腦環境 windows平臺qt6 下載 我這里直接提供官網下載地址:https://opencv.org/releases/ 我下載的是最新版,下載后是一個.exe文件…

單片機-STM32部分:15、直流電機與步進電機 PWM/IO

飛書文檔https://x509p6c8to.feishu.cn/wiki/InUfwEeJNimqctkyW1mcImianLh 一、步進電機與直流電機: 1-1、什么是直流電機? 直流電機是最常見的電機類型。直流電動機通常只有兩個引線,一個正極和一個負極。直流電機的轉速控制主要依靠改變輸…

「佰傲再生醫學」攜手企企通,解鎖企業采購供應鏈數字化新體驗

健康,是人類美好生活的基石。隨著“健康中國2030”規劃的深入推進,生物醫藥和再生醫學等前沿技術快速崛起,已成為促進全民健康、提升生命質量的重要支撐,為健康事業注入了新的希望和動力。 一、佰傲再生醫學,讓每個人…