線程的一些事(2)

在java中,線程的終止,是一種“軟性”操作,必須要對應的線程配合,才能把終止落實下去

然而,系統原生的api其實還提供了,強制終止線程的操作,無論線程執行到哪,都能強行把這個線程干掉。

這樣的操作Java的api中沒有提供的,上述的做法弊大于利,強行取結束一個線程,很可能線程執行到一半,會出現一些殘留的臨時性質的“錯誤”數據。

public class ThreadDemo12 {public static void main(String[] args) {boolean isQuit = false;Thread t = new Thread(() -> {while (!isQuit){System.out.println("我是一個線程,工作中!!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}//當前是死循環,給了個錯誤指示/*  System.out.println("線程工作完畢!");*/});t.start();try {Thread.sleep(3000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("讓t線程結束!");isQuit = true;}
}

我們將變量isQuit作為main方法中的局部變量。

彈出了警告,這就涉及到lambda表達式的變量捕獲了,當前捕獲的變量是isQuit所以對于isQuit來說,它要么加上final,要么不去進行修改。?

isQuit是局部變量的時候,是屬于main方法的棧幀中,但是Thread lambda是又自己獨立的棧幀的,這兩個棧幀的生命周期是不一致的

這就可能導致main方法執行完了,棧幀就銷毀了,同時Thread的棧幀還在,還想繼續使用isQuit--

在java中,變量捕獲的本質就是傳參,就是讓lambda表達式在自己的棧幀創建一個新的isQuit并把外面的isQuit的值拷貝過來(為了避免isQuit的值不同步,java就不讓isQuit來進行修改)

等待線程


多個線程的執行順序是隨機的,雖然線程的調度是無序的,但是可以通過一些api來影響線程執行的順序。

join就可以,

public class ThreadDemo14 {public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println("我是一個線程,正在工作中...");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("線程執行結束");});t.start();/* Thread.sleep(5000);*///這個操作就是線程等待t.join();System.out.println("這是主線程,期望這個日志在 t 結束后打印");}
}

這種方法比sleep方法要好很多,畢竟誰也不知道t線程啥時候結束,用join可以讓線程等 t 線程結束后再執行,這時候main線程的狀態就是“阻塞”狀態了。

Thread類基本的使用

1.啟動線程? ? ? ? start方法

理解 run 和 start 區別

2.終止線程? ? ? ? ? ? ? 核心讓run方法能夠快速結束

非常依賴 run 內部的代碼邏輯

Thread? ?isInterrupted(判定標志位)/interrupt(設置標志位)

如果提前喚醒sleep會清楚標志位

3.等待線程 join 讓一個線程等待另一個線程結束

線程之間的順序我們無法控制,但我們可以控制結束順序

獲取線程引用

Thread.currentThread()獲取到當前線程的 引用(Thread 的引用)

如果是繼承Thread,直接使用 this 拿到線程實例

如果不是則需要使用??Thread.currentThread();

線程的狀態

就緒:這個線程隨時可以去 cpu 上執行

阻塞:這個線程暫時不方便去cpu上執行

java中線程又以下幾種狀態:

1.NEW Thread 對象創建好了,但是還沒有調用 start 方法在系統中創建線程.
2.TERMINATED Thread 對象仍然存在,但是系統內部的線程已經執行完畢了
3.RUNNABLE 就緒狀態.表示這個線程正在 cpu 上執行,或者準備就緒隨時可以去 cpu 上執行4.TIMED WAITING 指定時間的阻塞, 就在到達一定時間之后自動解除阻塞.使用 sleep 會進入這個狀態.使用帶有超時時間的join也會
5.WAITING ?不帶時間的阻塞 (死等),必須要滿足一定的條件,才會解除阻塞
6.BLOCKED 由于鎖競爭,引起的阻塞,

?線程安全問題,來看下面的一段代碼

public class ThreadDemo19 {private static int count = 0;public static void main(String[] args) throws InterruptedException {//隨便創建個對象都行/*  Object locker = new Object();*///創建兩個線程,每個線程都針對上述 count 變量循環自增 5w次Thread t1 = new Thread(() ->{for (int i = 0; i < 50000; i++) {/*  synchronized(locker) {count++;}*/count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 50000; i++) {/*  synchronized(locker) {count++;}*/count++;}});t1.start();t2.start();t1.join();t2.join();//打印count結果System.out.println("count = " + count);}
}

?我們發現這個結果是錯的,我們計算的結果應該是100000.

這就涉及到線程安全問題了

count++是由三個指令構成的
1.load? ? ?從內存中讀取數據到cpu寄存器
2.add? ? ? 把寄存器中的值 + 1
3.save? ? ? 把寄存器的值寫回到內存中

對于單個線程是沒有這種問題的,但是對于多線程就會冒出來問題

我們發現預期是進行兩次count++后返回的count為2,但是因為兩個線程在讀取時出現了問題,第二個線程讀取的數據是還未進行更新的數據,這就導致出現了錯誤。

如果是這樣的順序自然沒有問題了

?我們需要的進行順序應該時等第一個線程save后第二個線程再進行load。

本質時因為線程之間的調度時無序的時搶占式執行

這就不得不提到String這個“不可變對象”了

1.方便JVM進行緩存(放到字符串常量池中)
2.hash值固定
3.線程安全的

線程不安全原因

1.根本原因? 操作系統上的線程時“搶占式執行”“隨即調度” => 線程之間執行順序帶來了很多變數

2.代碼結構? 代碼中多個線程,同時修改同一個變量

1.一個線程修改一個變量

2.多個線程讀取同一個變量

3.多個線程修改不同變量

這些都不會有事

3.直接原因 上述的線程修改操作本身不是’原子的‘

4.內存可見性問題

5.指令重排序問題

對于3這個問題我們可以找辦法來解決

1.對于搶占式執行修改,這是無法改變的事

2.對代碼結構進行調整,這是個辦法,但在有些情況下也是不適用的

3.可以通過特殊手段將著三個指令打包為一個“整體”,我們可以對其進行加鎖

加鎖

目的:把三個操作,打包成一個原子操作

進行加鎖的時候需要先準備好鎖對象,一個線程針對一個鎖對象加鎖后,當其他線程對鎖對象進行加鎖,則會產生阻塞(BLOCKED)(鎖沖突/鎖競爭),一直到前一個線程釋放鎖為止

要加鎖得用到synchronized。

?進入()就會加鎖(lock),出了{ }就會解鎖(unlock),synchronnized 是調用系統的 api 進行加鎖,系統api本質上是靠 cpu 上特定指令完成加鎖

當t1加鎖后,在沒解鎖的情況下,t2再想進行加鎖就會出現阻塞

在t1沒有解鎖的情況下,即使t1被調度出cpu,t2也還是在阻塞

即使這樣會影響到執行效率,但也比串行要快不少。

我們只是對count加鎖使得count串行,但for循環還是可以進行“并發”執行的

?

加鎖之后結果就正確了。

?對于對象的話只要不是同一個對象就不會有競爭這一說。

1.如果一個線程加鎖,一個不加,是不會出現鎖競爭的

2.如果兩個線程,針對不同的對象加鎖,也還是會存在線程安全問題

3.

把count放到一個 Test t 對象中,通過add來修改鎖對象的時候可以寫作this

相當于給this加鎖(鎖對象 this)

對于靜態方法的話相當于給類對象加鎖

我們可不可以加兩個鎖呢?

是否會打印hello?

?為啥會打印成功?不應該出現鎖沖突嗎?

當前由于是同一個線程,此時鎖對象,就知道了第二次加鎖的線程,就是持有鎖的線程,第二次操作,就可以直接放行不會出現阻塞。

這個特性被稱為“可重入”

一旦上述的代碼出現了阻塞,就稱為“死鎖”

可重入鎖就是為了防止我們在“不小心”中引入的問題

當我們在第一次加鎖的時候,計數器會進行加一操作,當第二次進行加鎖的時候,大仙加鎖的線程和持有鎖線程是一個線程,這個時候就會加鎖成功,并且計數器加一。

等到了計數器為0的時候才是真正的解鎖了,對于可重入鎖來說:

1.當前這個鎖是被哪個線程持有的

2.加鎖次數的計數器

計數器可以幫助線程清楚的記錄有幾個鎖。

加鎖能夠解決線程安全問題,但同時也引入了一個新的問題就是死鎖。

死鎖的三種典型場景

1.一個線程一把鎖

如果鎖是不可重入鎖,并且對一個線程對這把鎖進行加鎖兩次

2.兩個線程,兩把鎖

線程? 1? 獲得 鎖A

線程? ?2? 獲得 鎖B

接下來 1 嘗試獲取B, 2 嘗試獲取 A就同樣出現死鎖了!!!? ? ?

一旦出現“死鎖”,線程就“卡住了”無法繼續工作

public class ThreadDemo22 {public static void main(String[] args) {Object A = new Object();Object B = new Object();Thread t1 = new Thread(() -> {synchronized (A){//sleep一下是給t2時間讓t2也能拿到Btry {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//嘗試獲取B,并沒有釋放Asynchronized (B){System.out.println("t1拿到兩把鎖");}}});Thread t2 = new Thread(() -> {synchronized (B){//sllep一下,是給t1時間,讓t1能拿到Atry {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}//嘗試獲取A并沒有獲取Bsynchronized (A){System.out.println("t2拿到了兩把鎖");}}});t1.start();t2.start();}
}

就像這樣。

?3.N個線程M把鎖

哲學家就餐

?解決死鎖問題的方案

產生死鎖的四個必要條件

1.互斥使用,獲取鎖的過程是互斥的,一個線程拿到了這把鎖,另一個線程也想獲取,就需要阻塞等待。
2.不可搶占,一個線程拿到了鎖之后,只能主動解鎖,不能讓別的線程強行把鎖搶走。
3.請求保持,一個線程拿到了鎖 A 之后,在持有A的前提下,嘗試獲取B
4.循環等待,環路等待

由于四個都是必要條件,所以只要破環一個就解決問題了。

1,2.鎖最為基本的特性

3.代碼結構要看實際需求

4.代碼結構的,最為容易破壞

指定一定的規則,就可以有效的避免循環等待

1.引入額外的筷子

2.去掉一個線程

3.引入計數器,限制最多同時所少人吃飯

4.引入加鎖順序的規則

內存可見性引起的線程安全問題

public class ThreadDemo23 {private static int flag = 0;public static void main(String[] args) {Thread t1 = new Thread(() -> {while(flag == 0){}System.out.println("t1線程結束");});Thread t2 = new Thread(() -> {System.out.println("請輸入flag的值:");Scanner scanner = new Scanner(System.in);flag = scanner.nextInt();});t1.start();t2.start();}
}

運行代碼發現,并沒有我們想象的打印t1線程結束,而是直接不動了。

在這個過程中有兩個關鍵的點

1.load 操作執行的結果,每次都是一樣的(要想輸入,過幾秒才能輸入,在這幾秒都不知道循環都已經執行了上百億次了)
2.load 操作開銷遠遠超過 條件跳轉

訪問寄存器的操作速度,遠遠超過訪問內存

由于load開銷大,并且load的結果又一直沒有變化,所以jvm就會懷疑load操作有必要存在的必要嗎?

此時jvm就可能做出代碼優化,把上述load操作,給優化掉(只有前幾次進行load,后續發現,load反正都一樣,靜態分析代碼,也沒看到哪里改了flag,因此就把load操作,干掉了),干掉之后,就相當于不再重復讀內存直接使用寄存器之前”緩存“的值,大幅度的提高循環的執行速度

多線程的情況下很容易出現誤判,這里相當于 t2 修改了內存,但是 t1 沒有看到這個內存優化,就稱為”內存可見性“問題

我們發現在剛剛的代碼加上sleep就會執行成功,即使sleep時間有多小。?因為不加sleep一秒鐘可能循環上百億次,load開銷非常大,優化迫切程度就更高。

加了sleep,一秒鐘可能循環的次數就可能變為1000次,這樣load開銷相對來說就小了,所以優化迫切程度就想對來說就低了。

內存可見性問題,其實是個高度依賴編譯器優化的問題,啥時候觸發這個問題,都不知道

所以干脆希望不要出現內存可見性問題,將上述優化給關閉了

這就要使用關鍵字 volatile 來對上述的優化進行強制的關閉(雖然開銷大了,效率低了。但是數據準去性/邏輯正確性提高了)。

volatile 關鍵字

核心功能就是保證內存可見性(另一個功能進制指令重排序)

在上述的代碼中,編譯器發現,每次循環都要讀取內存,開銷太大,于是就把讀取內存操作優化成讀取寄存器操作,提高效率

在JMM模型的表述下

在上述代碼中,編譯器發現,每次循環都要讀取”主內存“,就會把數據從”主內存“中復制到”工作內存“中,后續每次都是讀取”工作內存“。?

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

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

相關文章

BGP實驗練習1

需求&#xff1a; 要求五臺路由器的環回地址均可以相互訪問 需求分析&#xff1a; 1.圖中存在五個路由器 AR1、AR2、AR3、AR4、AR5&#xff0c;分屬不同自治系統&#xff08;AS&#xff09;&#xff0c;AR1 在 AS 100&#xff0c;AR2 - AR4 在 AS 200&#xff0c;AR5 在 AS …

滑動窗口——將x減到0的最小操作數

題目&#xff1a; 這個題如果我們直接去思考方法是很困難的&#xff0c;因為我們不知道下一步是在數組的左還是右操作才能使其最小。正難則反&#xff0c;思考一下&#xff0c;無論是怎么樣的&#xff0c;最終這個數組都會分成三個部分左中右&#xff0c;而左右的組合就是我們…

C++ RAII機制

RAII&#xff08;Resource Acquisition Is Initialization&#xff09;是一種編程范式&#xff0c;核心思想是&#xff1a;資源的生命周期與對象綁定——對象創建時獲取資源&#xff0c;對象銷毀時自動釋放資源。這種機制通過構造函數和析構函數的配對執行&#xff0c;確保資源…

連續抵消解碼器--Successive Cancellation decoder(SC 解碼器)

在這里&#xff0c;我們來看一下&#xff08;Arikan&#xff0c;2009&#xff09;中提供的連續取消解碼算法。 顧名思義&#xff0c;SC解碼算法從u0開始按順序解碼比特。 凍結的比特節點總是被解碼為0。 在解碼ui時&#xff0c;根據以下規則使用由向量表示的可用比特來解碼u…

suricata之規則去重

一、環境和背景 1.1 環境 OS: Ubuntu 22.04.5 LTS IDE: vscode suricata: suricata 7.0.5 1.2 背景 在添加規則時&#xff0c;為了給規則分類&#xff0c;將不同類別的規則寫入不同的文件。 在規則加載時兩條不同的規則卻被認為是重復的&#xff0c;因此記錄一下去重邏輯。…

vue vite 無法熱更新問題

一、在vue頁面引入組件CustomEmployeesDialog&#xff0c;修改組件CustomEmployeesDialog無法熱更新 引入方式&#xff1a; import CustomEmployeesDialog from ../dialog/customEmployeesDialog.vue 目錄結構&#xff1a; 最后發現是引入import時&#xff0c;路徑大小寫與目…

深入理解 Linux 權限控制機制

引言 在 Linux 系統中&#xff0c;權限控制是保障系統安全的核心機制。通過限制用戶對文件和資源的訪問&#xff0c;它能有效防止未授權操作&#xff0c;保護數據不被篡改或泄露。合理設置權限不僅有助于實現用戶隔離和最小權限原則&#xff0c;還能降低系統被濫用或攻擊的風險…

Oracle版本、補丁及升級(12)——版本體系

12.1. 版本體系 Oracle作為最流行的一款關系數據庫軟件產品,其擁有自己一套成熟的版本管理體系。具體版本體系以12c為分界線,前后版本體系分別不同。 ???????12.1.1. 12c之前版本 12c之前的Oracle,版本共有5位阿拉伯數字組成,其中的每位數字,都有各自的含義,具…

Maven 中的 pom.xml 文件

目錄標題 1、根標簽 <project> 的直接子標簽順序?2、常見子標簽內部順序?2.1、<build> 標簽內部順序2.2、<dependencies> 標簽內部順序 3、modelVersion 為什么是 4.0.0 &#xff1f;4、<parent> 標簽&#xff1f;??4.1、為什么需要 <parent>…

方案精讀:華為與中軟-智慧園區解決方案技術主打膠片【附全文閱讀】

該文檔是華為與中軟國際的智慧園區輕量化解決方案推介&#xff0c;主要面向園區管理者、運營者及相關企業決策人。其核心圍繞園區痛點&#xff0c;闡述智慧園區的發展趨勢。 方案涵蓋綜合安防、便捷通行、設備管理等多領域應用場景&#xff0c;通過智能視頻監控、人臉識別、遠程…

# KVstorageBaseRaft-cpp 項目 RPC 模塊源碼學習

KVstorageBaseRaft-cpp 項目 RPC 模塊源碼學習 。 一、項目簡介 KVstorageBaseRaft-cpp 是一個基于 Raft 一致性算法實現的分布式 KV 存儲系統&#xff0c;采用 C 開發。項目的核心目標是幫助開發者理解 Raft 原理和分布式 KV 存儲的基本實現。RPC 模塊是分布式系統通信的關…

TeledyneLeCroy在OFC2025 EA展臺上展示了其400G/800G的全包圍的測試解決方案,滿足了UEC聯盟和UALINK聯盟的技術需求

Teledyne LeCroy在OFC 2025上的EA展臺 在2025年3月26日至28日于美國圣地亞哥舉辦的OFC&#xff08;Optical Fiber Communication Conference and Exhibition&#xff09;展會上&#xff0c;全球領先的測試測量解決方案提供商Teledyne LeCroy隆重展示了其最新研發的800G網絡測試…

新一代電動門“攻克”行業痛點,遠峰科技打造“智能出入”新標桿

在2025上海國際車展期間&#xff0c;遠峰科技舉辦了一場面向車企合作伙伴和媒體的智能汽車解決方案實車展示會。 在這其中&#xff0c;遠峰科技的新一代電動門首次亮相&#xff0c;突破性的解決了行業普遍存在的“運行抖動不平順”、“窄車位車門開度過小”、“障礙物識別不準…

WEB UI自動化測試之Pytest框架學習

文章目錄 前言Pytest簡介Pytest安裝Pytest的常用插件Pytest的命名約束Pytest的運行方式Pytest運行方式與unittest對比主函數運行命令行運行執行結果代碼說明 pytest.ini配置文件方式運行&#xff08;推薦&#xff09;使用markers標記測試用例 pytest中添加Fixture&#xff08;測…

機器學習簡單概述

Chatgpt回答&#xff1a; 機器學習&#xff1a;機器學習是人工智能的一個分支&#xff0c;側重于通過數據訓練模型&#xff0c;使計算機能夠根據數據進行預測、分類、回歸等任務。它通過算法從歷史數據中學習規律&#xff0c;然后在新數據上進行推斷。機器學習包括多種算法&…

openjdk底層匯編指令調用(一)——匯編指令及指令編碼基礎

匯編指令 計算機在執行過程時只識別代表0或者1的電信號。因此為了讓計算機能夠執行則須向計算機輸入一系列01構成的指令。 例如在x64平臺下&#xff0c;0x53&#xff0c;二進制為01010011&#xff0c;表示將rbx寄存器中的值壓棧。 但是&#xff0c;對于程序員而言&#xff0c;…

Python Day 22 學習

學習講義Day14安排的內容&#xff1a;SHAP圖的繪制 SHAP模型的基本概念 參考學習的帖子&#xff1a;SHAP 可視化解釋機器學習模型簡介_shap圖-CSDN博客 以下為學習該篇帖子的理解記錄&#xff1a; Q. 什么是SHAP模型&#xff1f;它與機器學習模型的區別在哪兒&#xff1f; …

48.輻射發射RE和傳導發射CE測試方法分析

輻射發射RE和傳導發射CE測試方法分析 1. 所有測試項目總結2. 輻射發射RE測試方法3. 傳到發射CE測試方法 1. 所有測試項目總結 所有EMC測試項目都是基于模擬現實的。 模擬現實中可能發生的各種真實場景&#xff0c;然后統計總結出各種場景下的真實應力&#xff0c;并通過制造測…

在 Vue 3 中實現刮刮樂抽獎

&#x1f389; 在 Vue 3 中實現刮刮樂抽獎 當項目中需要做一些活動互動頁時&#xff0c;需要實現刮刮樂&#xff0c;請看如下效果&#xff1a; 這里感謝github用戶Choicc分享的組件&#xff0c;具體可點擊傳送門查看 1. 引入組件 將/src/components下ScratchCard.vue復制到自…

c語言第一個小游戲:貪吃蛇小游戲01

hello啊大家好 今天我們用一個小游戲來增強我們的c語言&#xff01; 那就是貪吃蛇 為什么要做一個貪吃蛇小游戲呢&#xff1f; 因為這個小游戲所涉及到的知識有c語言的指針、數組、鏈表、函數等等可以讓我們通過這個游戲來鞏固c語言&#xff0c;進一步認識c語言。 一.我們先…