【Java ee初階】多線程(5)

一、wait 和 notify

wait notify 是兩個用來協調線程執行順序的關鍵字,用來避免“線程餓死”的情況。

wait? 和 notify 其實都是 Object 這個類的方法,而 Object這個類是所有類的“祖宗類”,也就是說明,任何一個類,都可以調用wait 和notify這兩個方法。

Java標準庫中,涉及到阻塞的方法,都可能拋出InterruptedException

讓我們來觀察一下這個異常的名字。

Illegal:非法的,不正確的,不合理的(而不是違反法律的)、

Monitor:監視器/顯示器(電腦的顯示器,英文不是screen,而是Monitor)此處的Monitor指的是synchronized,synchronized在JVM里面的底層實現,就被稱為“監視器鎖”(JVM源碼,變量名是Monitor相關的詞)

所以這個異常的意思是,當前處于非法的鎖狀態。

眾所周知,鎖一共有兩種狀態,一種是加鎖,一種是解鎖。

wait方法內部做的第一件事情,就是釋放鎖。

而我們必須要先得到鎖,才能去談釋放鎖。因此,wait必須放到synchronized代碼塊內部去進行使用。

此處的阻塞會持續進行,直到其他線程調用notify把該線程進行喚醒。

此處的阻塞會持續進行,直到其他線程調用notify,將該線程進行喚醒。

package Thread;import java.util.Scanner;public class demo28 {// 將 object 變量移到類內部并添加 static 修飾符public static Object object = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {synchronized (object) { System.out.println("t1 wait之前");try {object.wait(); } catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 wait之后"); }});Thread t2 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("請輸入任意內容 嘗試喚醒t1");scanner.next(); synchronized (object) { object.notify(); System.out.println("t2 notify之后"); }});     t1.start();t2.start();}   
}

輸出:

圖上這四處地方的鎖,必須是同一個對象。

假設notify后面又有一堆別的邏輯,此時,這個鎖就會再多占有一會。

【總結】wait要做的事情:

1、使當前執行代碼的線程進行等待(把線程放到等待隊列中去)

2、釋放當前的鎖

3、滿足一定條件的時候被喚醒,并且重新嘗試獲取這把鎖

(這三個步驟是同時進行的)

使用wait的時候,阻塞其實是有兩個階段的:

1、WAITING的阻塞:通過wait 等待其他線程的通知

2、BLOCKED阻塞:當收到通知之后,就會重新嘗試獲取這把鎖。重新嘗試獲取這把鎖,很可能又會遇到鎖競爭

wait進行阻塞之后,需要通過notify喚醒。默認情況下,wait的阻塞也是死等。

這樣子是不合理的,因此,我們在工作中需要設定等待時間上限。(超過時間)

括號里的等待時間是毫秒

package Thread;import java.util.Scanner;public class demo29 {public static Object locker = new Object();public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {synchronized (locker) {System.out.println("t1 wait之前");try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 wait之后");}});Thread t2 = new Thread(() -> {synchronized (locker) {System.out.println("t2 wait之前");try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2 wait之后");}});Thread t3 = new Thread(() -> {synchronized (locker) {System.out.println("t3 wait之前");try {locker.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t3 wait之后");}});Thread t4 = new Thread(() -> {Scanner scanner = new Scanner(System.in);System.out.println("請輸入任意內容 嘗試喚醒t1、t2、t3");scanner.next();synchronized (locker) {System.out.println("t4 notify之前");locker.notify(); // 喚醒一個在 locker 上等待的線程,這里是 t1System.out.println("t4 notify之后");}});t1.start();t2.start();t3.start();t4.start();}}

輸出:

可以看出,當前只是將t1喚醒了

再次嘗試

喚醒的仍然是t1

咱們在多線程中談到的“隨機”其實不是數學上概率均等的隨機,這種隨機的概率是無法預測的。取決于調度器如何去調度。調度器里面,其實不是“概率均等的喚醒”,調度器內部也是有一套規則的。這套規則,對于程序員是“透明的”,程序員做的,就是不能依賴于這里的狀態。

mysql的時候,select查詢一個數據,得到的結果集,是按照怎樣的順序的呢?(是按照id的順序,時間的順序,排列的順序的嗎?)都不是,mysql就沒有這樣的承諾。必須加上orderby

notifyAll可以喚醒全部:

如果沒有任何對象在wait,那么直接調用notify / notifyAll 會發生什么?
不會發生任何事情,直接憑空調用notify是沒有任何副作用的

經典面試題:

請你談一談sleep? 和 wait 的區別

1.wait 的設計就是為了提前喚醒。超時時間,是“后手”(B計劃)

sleep 的設計就是為了到達時間再進行喚醒。雖然也可以通過Interrupt()進行提前喚醒,但是這樣的喚醒是會產生異常的。(此處的異常表示:程序出現不符合預期的情況,才稱為“異常”)

2.wait需要搭配鎖來時進行使用,wait執行時會先釋放鎖

?sleep不需要搭配鎖進行使用,當把sleep放到synchronized內部的時候,不會釋放鎖(抱著鎖睡覺)

綜上所述,在實際開發中,wait比sleep用的更多。

二、單例模式

單例模式是一種設計模式,校招中最常考到的設計模式之一。

為了使得新手的代碼下線也能夠有所保證,大佬們研究出了一些“設計模式”,用來解決一些固定的場景問題,這些問題有著固定的套路。

如果按照設計模式寫,能夠得到一個較為靠譜的代碼,屬于是一種軟性要求。

設計模式有很多很多種類,不僅僅有23種。

單例模式 :單例,也就是單個實例(單個對象)。雖然一個類,在語法角度來說,是可以無限創建實例的,但是在實際的場景當中,可能有時候我們只希望這個類只有一個實例(例如JDBC)

那么,在Java代碼中,如何實現單例模式呢?——有很多種實現方式,其中最主要的有兩種模式:

1、餓漢模式

創建實例的時機是非常緊迫的。

由于此處的Instance是一個static 成員,創建時機,就是在類加載的時候。也就是說,程序一啟動,實例就被創建好了。

package Thread;class Singleton{private static Singleton instance = new Singleton();public static Singleton getInstance(){return instance;}   //做了一個“君子協定”,讓其他類不能new這個類,只能通過getInstance()方法獲取這個類的實例。private Singleton(){}
}public class demo33 {public static void main(String[] args) {Singleton instance1 = Singleton.getInstance();Singleton instance2 = Singleton.getInstance();System.out.println(instance1 == instance2);}}

? 做了一個“君子協定”,讓其他類不能new這個類,只能通過getInstance()方法獲取這個類的實例。

2、懶漢模式

第一次使用這個實例的時候,才會創建這個實例,創建的時機更晚

上述兩份代碼,哪一份是線程安全的,哪一份是線程不安全的呢?

而懶漢模式容易因此下述問題:

最終只創建了一個實例!

實際開發中,單例類的構造方法可能是一個非常重量的方法。我們之前,代碼中也有單例模式的使用。當時通過單例類,管理整個服務器程序所以來的所有數據(100G)。這個實例創建的過程,就會從硬盤上把100G的數據加載到內存當中。

那么,如何解決該問題呢?

我們可以通過加鎖,將操作打包成原子的來解決該問題。

但是,這串代碼仍然存在問題:邏輯上來看,我們只是在第一次調用的時候,才會涉及到線程安全問題,只要對象創建完畢,后序都是直接return了,就不涉及修改了。但是,此時這個代碼,鎖是每次調用都會加上的。明明已經線程阿耐庵了,但是還要再進行加鎖,這并不合理。

圖中,一摸一樣的條件連續寫了兩遍。以前都是一個線程,這個代碼執行下來,第一次判定和第二次判定,結論是一定相同的。

而現在是多線程,第一次判定和第二次判定,結論可以不一樣。因為再第一次和第二次判定之間,可能有另外一個線程,修改了instance。多線程,打破了以前的認知。而后面學習的網絡,EE進階里面的框架,也會打破以前的認知。

在多線程當中,指令重排序容易引起線程安全問題。指令重排序是編譯器優化的一種手段,這是編譯器在確保邏輯一致的情況下,為了提高效率,調整代碼的順序,就可以讓效率變高了。然而,指令重排序在遇見多線程就又出現問題了。

此處涉及到的指令是非常多的,為了簡化這個模型,我們將他抽象成三個步驟:

1、申請內存空間

2、在內存空間上進行初始化(構造方法)

3、內存地址,保存到引用變量當中

在多線程中,由于指令重排序,容易引起上述問題。

Instance里面還沒有任何的屬性方法,但是已經被線程2拿去使用了!

那么,如何避免指令重排序的問題呢?

只需要加上volatile這個關鍵字。

volatile的意思是,針對這個變量的讀寫操作,不要觸發優化。

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

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

相關文章

基于k8s的Jenkins CI/CD平臺部署實踐(二):流水線構建與自動部署全流程

基于k8s的Jenkins CI/CD平臺部署實踐(二):流水線構建與自動部署全流程 文章目錄 基于k8s的Jenkins CI/CD平臺部署實踐(二):流水線構建與自動部署全流程一、Jenkins簡介二、系統架構與環境說明1. 系統架構2.…

《Windows 環境下 Qt C++ 項目升級 GCC 版本的完整指南》

Windows 環境下 Qt C++ 項目升級 GCC 版本的完整指南 在 Windows 系統中升級 Qt C++ 項目的 GCC 版本需要同時考慮 Qt 工具鏈、MinGW 環境以及項目配置的調整。以下是詳細的升級步驟和注意事項: 一、升級前的準備工作 1. 確認當前環境 檢查 Qt 版本(建議使用 Qt 5.15+ 以獲…

【coze】故事卡片(圖片、音頻、文字)

【coze】故事卡片(圖片、音頻、文字) 1、創建智能體2、添加人設與回復邏輯3、添加工作流(1)創建工作流(2)添加大模型節點(3)添加提示詞優化節點(4)添加豆包圖…

Maven 依賴發布與倉庫治理

🧑 博主簡介:CSDN博客專家,歷代文學網(PC端可以訪問:https://literature.sinhy.com/#/?__c1000,移動端可微信小程序搜索“歷代文學”)總架構師,15年工作經驗,精通Java編…

虛擬現實視頻播放器 2.6.1 | 支持多種VR格式,提供沉浸式觀看體驗的媒體播放器

虛擬現實媒體播放器是一款專為在智能手機上播放VR(虛擬現實)照片和視頻而設計的應用程序。它支持多種格式的影像內容,包括360和180等距矩形柱面、標準鏡頭和魚眼鏡頭拍攝的照片和視頻,并且兼容3D立體并排、上/下以及收縮媒體格式。…

ts axios中報 Property ‘code‘ does not exist on type ‘AxiosResponse<any, any>‘

ts語法有嚴格的格式,如果我們在處理響應數據時,出現了axios響應中非默認字段,就會出現標題那樣的警告,我們可以通過創建axios.dt.ts解決這個問題 下面是我在開發中遇到的警告,code并不是axios默認返回的字段&#xff0…

tinyrenderer筆記(Shadow Mapping)

tinyrenderer個人代碼倉庫:tinyrenderer個人練習代碼 前言 陰影是光線被阻擋的結果;當光源的光線由于其他物體的阻擋而無法到達物體表面時,該物體就會產生陰影。陰影能使場景看起來更真實,并讓觀察者獲得物體之間的空間位置關系。…

debian中筆記本的省電選擇auto-cpufreq

在reddit中,看評論區出現這個軟件,于是打算嘗試一下,應該能對不使用電源時筆記本的省電起到一定的作用。 https://github.com/AdnanHodzic/auto-cpufreq?tabreadme-ov-file#why-do-i-need-auto-cpufreq 作用 One of the problems with Linux…

Windows 查看電腦是否插拔過U盤

1、按 “WinR” 組合鍵打開 “運行” 對話框,輸入 “regedit” 并回車,打開注冊表編輯器。 2、依次展開HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Enum\USBSTOR注冊表項,這里記錄了所有已連接過的 USB 設備信息,包括 U 盤&am…

426、N叉樹的層序遍歷

輸入檢查: if not root:return [] 如果根節點為空,直接返回空列表 初始化: result [] queue collections.deque([root]) result用于存儲最終結果queue初始化包含根節點,使用雙端隊列實現 主循環: while queue:leve…

【ES】Elasticsearch字段映射沖突問題分析與解決

在使用Elasticsearch作為搜索引擎時,經常會遇到一些映射(Mapping)相關的問題。本文將深入分析字段映射沖突問題,并通過原生的Elasticsearch API請求來復現和解決這個問題。 問題描述 在實際項目中,我們遇到以下錯誤: Transport…

小紅書怎么看自己ip地址?小紅書更改ip地址教學

在社交媒體高度透明的今天,小紅書等平臺公開用戶IP屬地的功能引發了廣泛討論。無論是出于隱私保護的擔憂,還是因需要切換屬地,許多用戶都迫切想知道:能否通過手動修改“偽裝”所在地? 事實上,IP屬地可能影…

深入理解 Java 觀察者模式:原理、實現與應用

在軟件開發領域,設計模式堪稱開發者智慧的凝練結晶,它們為解決各類常見編程難題提供了行之有效的方案。觀察者模式(Observer Pattern)作為行為型設計模式的重要一員,在處理對象間依賴關系與事件通知方面表現卓越。本文…

網絡原理 TCP/IP

1.應用層 1.1自定義協議 客戶端和服務器之間往往進行交互的是“結構化”數據,網絡傳輸的數據是“字符串”“二進制bit流”,約定協議的過程就是把結構化”數據轉成“字符串”或“二進制bit流”的過程. 序列化:把結構化”數據轉成“字符串”…

2025年5月HCIP題庫(帶解析)

某個ACL規則如下:則下列哪些IP地址可以被permit規則匹配: rule 5 permit ip source 10.0.2.0 0.0.254.255 A、10.0.4.5 B、10.0.5.6 C、10.0.6.7 D、10.0.2.1 試題答案:A;C;D 試題解析: 10.0.2.000001010.00000000.00000010.0000000…

【Redis | 基礎總結篇 】

目錄 前言: 1.Redis的介紹: 2.Redis的類型與命令: 3.Redis的安裝: 3.1.Windows版本 3.2.Linux版本 4.在java中使用Redis: 4.1.介紹 4.2.Jedis 4.3.Spring Data Redis 前言: 本篇主要講述了Redis的…

38.前端代碼拆分

因為前端代碼之前是一體編寫的,所以為了方便對代碼進行了拆分 之前是這樣的: 為了更加規范,所以拆分成vue、util、store、api等部分: css: store: 拆分后的大致界面為: 其實還有點別扭需要后續再調整

tinyrenderer筆記(Shader)

tinyrenderer個人代碼倉庫:tinyrenderer個人練習代碼 前言 現在我們將所有的渲染代碼都放在了 main.cpp 中,然而在 OpenGL 渲染管線中,渲染的核心邏輯是位于 shader 中的,下面是 OpenGL 的渲染管線: 藍色是我們可以自…

C++高性能內存池

目錄 1. 項目介紹 1. 這個項目做的是什么? 2. 該項目要求的知識儲備 2. 什么是內存池 1. 池化技術 2. 內存池 3. 內存池主要解決的問題 4.malloc 3. 先設計一個定長的內存池 4.高并發內存池 -- 整體框架設計 5. 高并發內存池 -- thread cache 6. 高并發內存池 -- …

LintCode407-加一,LintCode第479題-數組第二大數

第407題: 描述 給定一個非負數,表示一個數字數組,在該數的基礎上1,返回一個新的數組。 該數字按照數位高低進行排列,最高位的數在列表的最前面. 樣例 1: 輸入:[1,2,3] 輸出:[1,2,4] 樣例 …