多線程 -- Thread類

Thread 的常見構造方法

最后一個構造方法中的 ThreadGroup 線程組是 Java 中的概念,和系統內核中的線程組不是一個東西。我們自己創建的線程,默認是按照 Thread- 0 1 2 3 4...命名。但我們也可以給不同的線程,起不同的名字(不同的名字,對線程的執行,沒有什么影響,主要是方便我們調試)

舉例如下:

Thread 的幾個常見屬性

  1. ?ID 是線程的唯一表示,JVM會自動進行分配,不同線程不會重復
  2. ?名稱是各種調試工具中,會使用到
  3. ?狀態表示線程當前所處的一個情況,進程有狀態,分為就緒狀態和阻塞狀態。線程也有狀? 態,Java中對線程的狀態,又進行了進一步的區分(比系統原生的狀態,更豐富一些)
  4. 線程也有優先級,優先級高的線程,理論上來說更容易被調度到,但在Java中,效果其實并不是很明顯(會對內核調度器的調度過程產生一些影響),總體上還是搶占式調度。
  5. daemon --> 線程守護,也可以稱為是”后臺線程“,和其對應的,還有”前臺進程“(注意,這里的前臺和后臺,與Android系統上的前后臺APP是完全不同的)

前臺線程的運行,會阻止進程結束,后臺線程的運行,不會阻止進程的結束

示例如下:

我們可以打開 jconsole 觀察一下:

?上面示例代碼在執行過程中, t 會持續進行(因為是while(true)死循環),但 main 已經結束了。jconsole 觀察中,可以看到,除了我們創建的線程 Thread-0,其他都是JVM內置的線程,那些都是后臺線程,不會阻止進程的結束。并且,在列表中,已經沒有 main 線程了。按照我們之前的理解,main 執行完畢,進程應該結束,但很明顯,此時這個進程仍然在繼續執行中!

當我們強制結束,打印臺線程顯示如下的話,才表明進程結束了。

我們代碼創建出來的進程,默認就是前臺線程,會阻止進程結束,只要前臺線程沒執行完,進程就不會結束,即使 main 已經執行完畢了

但我們若是進行一些稍加改動,即在調用 start 方法之前,就調用 setDaemon 方法,設置進程為后臺進程?

此時在重新運行程序,就會發現,控制臺什么都沒打印,進程就結束了。

setDaemon方法中,傳入參數為 true ,則該線程為后臺,不設 true,則是前臺。

后臺不會阻止進程的結束,前臺會阻止進程的結束。

? ? ? ? 6.isAlive(),即該線程是否存活表示了內核中的線程(PCB)是否還存在,Java代碼中定義的線程對象(Thread)實例,雖然表示一個線程,但這個對象本身的生命周期,和內核中的 PCB 聲明周期,是完全不一樣的。

當執行這段代碼之后,此時 t 對象是有了,但是內核中 PCB 還沒有,isAlive 就是 false。

真正 t 調用 start方法,即 t.strat() 的時候,才真正在內核中 創建出這個PCB,此時 isAlive就是 ture了。當線程 run 執行完了,此時 內核中的線程 就結束了(內核PCB 就釋放了),但是,此時 t 變量還存在,但 isAlive是 false。

示例代碼:

打印結果:

Thread 類 使用 start 方法,啟動一個線程,對于同一個 Thread 對象來說,start 方法只能調用一次。

示例代碼:

運行程序之后:

雖然可以正常打印,但是會有報錯的Exception

我們可以分析一下上面這個異常,IllegalThreadStateException,即非法的線程狀態異常

面試題:start 和 run 的區別

本質上,strat 和 run 是八竿子打不著,互不相干的內容。

如圖,我們有一個這樣的代碼:

在 main 函數中,調用 start 方法,結果如下:

如果注釋掉 strat 方法,調用 run 方法,結果如下:

這里看起來的執行結果是一樣的。但兩個方法打印的時候,操作所在的線程的不一樣的

t.strat() --> 這行代碼是創建一個新的線程,由新的線程執行 hello

t.run() --> 這行代碼的操作,仍然是在主線程中,打印的 hello

如果我們對代碼進行一些修改:

打印結果就只有 hello thread,即代碼此時就只能停留在 run 的循環中,下方 main 中的循環(打印 hello main 是無法執行的)

但如果此時是調用 t.start()

結果如下:

就會創建一個新的進程,然后在進程里面執行run循環,但因為 Java 是搶占式進程,此時就能夠執行 main 中的循環。

終止一個線程

李四?旦進到?作狀態,他就會按照?動指南上的步驟去進??作,不完成是不會結束的。但有時我 們需要增加?些機制,例如?板突然來電話了,說轉賬的對?是個騙?,需要趕緊停?轉賬,那張三 該如何通知李四停?呢?這就涉及到我們的終止線程的?式了。

終止一個線程:即,讓線程 run 方法(入口方法)執行完畢

那如何讓線程提前終止呢?

核心問題也就是:如何讓 run 方法能夠提前結束呢?這就很取決于我們具體代碼的實現方式了。

目前常見的有一下兩種方式:

? ? ? ? 1.通過共享的標記來進行溝通

? ? ? ? 2.調用 interrupt() 方法來通知

引入:

我們也可以引入一個標志位 isQuite 如下圖

通過上述代碼,就可以讓線程結束掉,具體什么時候結束,就取決于我們在另一個線程中的代碼實現(即,在另一個線程中何時修改 isQuite 的值)

還有就是,在 main 線程中,要想讓 t 線程結束,大前提,一定是 t 線程中的代碼,對這樣的邏輯有所實現,即有 isQuite 這種標志位,而不是 t 里面的代碼隨便怎么寫,都能夠隨意提前結束的。

通過剛才的寫法,其實是并不夠優雅的,雷軍好同志曾經說過,他大學期間的代碼,優雅到詩一般,我們這個就比較拉跨了。

Thread 類還提供了一種更優的選擇 --> ?Thread 對象,內置了一個變量 --> currentThread

改進代碼如下:

在這個代碼中 while 循環中的參數是 Thread.currentThread().isInterrupted()?

其中,Thread.currentThread 操作是獲取當前線程實例( t ),那個線程調用,得到的就是那個線程的實例,類似于 this,把我們引入中的 isQuite 改成判定 isInterrupter。

Thread.currentThread 補充:

該方法是獲取到當前線程的引用(Thread的引用),如果是繼承 Thread 類,就直接可以使用 This 來拿到線程實例,如果是 Runnable 或者 lambda 的方式,this 就無能為例了,此時 this 已經不再指向 Thread 對象了,就只能使用 Thread.currentThread()了。

下面的代碼,本質上,是使用了 Thread 實例,內部自帶的標志位,來代替剛才手動創建的 isQuit變量了,最后一行代碼 t.interrupt() 就相當于 isQuit = true了。

執行代碼如下:

可以看到,代碼執行到了14行的時候,出現了一個異常,并且 t 線程 并沒有真的結束。

我們研究報出的異常 InterruptedException 這不就是 try - catch 中的嗎?

再觀察報出的異常:

好像是這里的 interrupt 導致 sleep 出現了異常

如果沒有 sleep interrupt ,線程是可以順利結束的,但有了 sleep 就引起了變數。

在執行 sleep 的過程中,調用了 interrupt,大概率是 sleep 的休眠時間還沒有到,就被 interrupt 提前喚醒了。

sleep 提前被喚醒,會做兩件事:

? ? ? ? 1. 拋出 InterruptedException (緊接著就會被 catch 獲取到)

? ? ? ? 2. 清除 Thread 對象的 isInterrupted 標志位

通過 interrupt 方法,已經把標志位設置位 true 了,但是 sleep 提前被喚醒之后,又會清除 Thread 對象的 isInterrupted 標志位,即又把標志位設回 false 了,所以此時循環還是會繼續執行了。

如果我們想要讓線程結束的話,只需要在 catch 中 加上 break 就可以了。

結果如下:

這樣,循環就可以結束了。但還是會報出Exception,但這個日志是我們代碼中 e.printStackTrace()中打出來的,如果我們不寫打印,就不會存在了。

sleep 清空標志位,是為了給程序員更多的“可操作空間”的。前一個代碼,寫的是 sleep(1000),結果現在, 1000 還沒有到,就要終止線程,這就相當于是兩個前后矛盾的操作,此時,也是需要更多的代碼,來對這樣的情況進行具體處理的。

此時程序員就可以在 catch 語句中,加入一些代碼,來做一些處理。

? ? ? ? 1. 讓線程立即結束 --> break

? ? ? ? 2. 讓線程不結束,繼續執行 --> 不加 break

? ? ? ? 3. 讓線程執行一些邏輯之后,再結束 --> 寫一些其他的代碼,再 break

對 try - catch 塊的補充:(在實際開發中, catch 里應該要寫什么樣的代碼???如果程序出現了異常,該如何處理,是更加合理的???)

對于一個服務器來說,穩定性,是十分重要的,我們無法保證服務器一直不出問題,這些所謂的“問題”,在 Java 代碼中,就會以 異常的形式體現出來,可以通過 catch 語句,對這些異常進行處理。

? ? ? ? 1. 嘗試自動恢復。能自動恢復,就盡量自動恢復。比如出現了一個網絡通信相關的異常,我們就可以在 catch 中嘗試重新連接網絡。

? ? ? ? 2. 記錄日志(異常信息記錄到文件中)有些情況,并非是很嚴重的問題,只需要把這個問題記錄下來即可(并不需要立即解決),等到后面程序員有空閑的時候,再進行解決。

? ? ? ? 3.發出報警。這個是針對一些比較嚴重的問題了,包括但不限于,給程序員 發郵件,發短信,發微信,打電話等等.......

? ? ? ? 4. 也有少數正常的業務邏輯,會依賴到 catch (比如文件操作中 有的方法,就是要通過 catch 來結束循環...)(非常規用法)

在 Java 中, 線程的終止,是一種“軟性”操作,必須要對應的線程去進行配合,才可以把終止落實下去。

相比之下,系統原生的 API 其實提供了強制終止線程的操作。無論線程是否愿意配合,無論線程執行到了那行代碼,都能夠強行的把線程給干掉!!

這樣的操作,Java? 的 API 是沒有提供的,上述強制執行的做法,利大于弊。

如果要強行終止一個線程,很可能線程執行到一般,就被強制終止,會出現一些殘留的臨時性質的“錯誤”的數據。比如這個線程正在執行寫操作,寫文件的數據有一定的格式要求(寫一個圖片文件) --> 如果寫圖片寫了一般,線程被終止了,圖片就尷尬了,圖片文件是存在的,里面的內容不正確,無法正確打開了。

private static boolean isQuit = false

如果把 isQuit 作為 main 方法中的局部變量,是否可行? -- > 不可行。

這是我們在 lambda 表達式中曾經研究過的一個語法 -- > 變量捕獲

lambda 表達式 / 匿名內部類 是可以訪問到 外面定義的局部變量的(變量捕獲規則)

報錯信息告訴我們,捕獲的變量,必須是 final 修飾的 或者是 “事實”final(即雖然沒寫 final 但是沒有修改), 但is Quit 又必須要修改!!!此處的 final,也不是“試試”final,所以局部變量這一手,是行不通的。

因此,必須寫成成員變量。那為什么,寫成成員變量就行得通了呢?這又是那個語法規則呢?

lambda表達式,本質上是“函數式接口“ ==》 匿名內部類。 內部類來訪問外部類的成員,這個事情本身就是可以的,這個操作就不受到變量捕獲的影響了。

那為什么,Java 對于變量捕獲操作,有 final 的限制呢???

isQuite 是局部變量的時候,是屬于 main 方法的棧幀中,但是 Thread lambda 是由自己獨立的棧幀的(是另一個線程中的方法),這兩個棧幀的生命周期是不一致的。

這就可能導致 --> main 方法執行完了,棧幀就銷毀了,main 方法執行完了,棧幀就銷毀了,但此時 Thread 的棧幀還在,還想繼續使用 isQuit。Java 中的做法就非常的簡單粗暴,變量捕獲的本質上就是傳參,換句話說,就是讓 lambda 表達式在自己的棧中創建一個新的 isQuit,并把外面的 isQuit 值給拷貝過來(為了避免 isQuit 的值不同步, Java 干脆就不讓 isQuit 修改)。

等待一個線程 - join()

有時候,我們需要等待一個線程完成它的工作之后,才能進行自己的下一步工作。例如:張三只有等李四轉賬成功之后,才能對現在的吃飯行為進行付款,這時候,我們需要一個方法明確的等待線程的結束。

多個線程的執行順序是不固定的(隨即調度,搶占式執行),雖然線程底部的調度是無序的,但是可以在應用程序中,通過一些 API,來影響到線程執行的順序。 --> join 就是一種方式,影響線程結束的先后順序。比如,t2 線程等待 t1 線程,此時,一定是 t1 線程先結束,t2 線程后結束,其中就使用到 join 使得 t2 線程阻塞。

示例代碼:

打印結果如下:

補充:

如果不適用 join,使用 sleep,是具有隨機性的,如果將 join 換位 sleep,如下:

在 sleep?5 秒之后,是先打印”這是主線程“還是先打印”線程執行完畢“,是無法確定的。雖然,我們可以進行修改,sleep 中的參數可以傳為 6000,這也是一個辦法,但是不完全可行,我們給 sleep 傳參數,是能夠對線程 t 的執行時間有一個預期,才能這樣些,如果都不知道 t 要執行多久,那 sleep 的參數就沒辦法傳了。所以最好的辦法還是 join 方法,讓 main 線程等待 t 線程結束【誰等誰,一定要分清楚,在那個線程中調用 join 方法,就是在 那個線程中等待 調用 join 方法的線程,如上圖例子,在 main 線程中,t 線程調用 join 方法,則是 main 線程 等待 t 線程】。

執行 join 的時候,就看 t 線程是否正在運行,如果 t 運行中,main 線程就會阻塞(main 線程就暫時不去參與 CPU 執行了),如果 t 運行結束, main 線程就會總阻塞中恢復過來,并且繼續往下執行。(阻塞:使得線程的結束時間,產生了先后關系。)

補充:

? ? ? ? 1.這個 join 阻塞和優先級還是不同的。優先級,是系統調度器在內核中完成的工作,即使優先級有差異,但是每個線程的執行順序仍然是隨機的。

線程優先級是調度器的重要參考,但實際執行順序還受調度策略、時間片、線程狀態、資源競爭等因素影響。優先級決定的是線程獲取 CPU 的 “機會”,而非絕對順序。因此,即使優先級有差異,線程執行順序仍可能表現出隨機性。

上述線程結束順序的先后,在代碼中,是通過 API 來控制的,讓 main 線程,主動放棄了去調度器中調度,其中 t 線程 雖然也可能和其他線程共同進行調度,但由于主線程一直在等待,即使 t 線程中間經歷了多次 CPU 的切換,仍然不影響 t 線程最終能夠正確先執行完畢。

join 方法中,也是可以有參數的,若沒有參數,我們稱為“死等”,就必須要要等待線程結束,再進行當前線程,這是機器不科學的,尤其是再我們的計算機中(如果我們的代碼中,因為死等,導致程序卡住了,無法繼續處理后面的邏輯,這是一個非常嚴重的 bug !)

若傳入一個參數,就是帶有超時時間的等,等操作是由一個時間上限的,等待的時間達到超時時間,就不等了,該干啥干啥了。

完!

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

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

相關文章

Java基礎關鍵_032_反射(二)

目 錄 一、反射 Class 的 Method 1.反編譯 String 類的方法簽名 2.通過反射調用方法 3.反編譯 String 類的構造方法簽名 4.通過反射調用構造方法 二、類加載過程 1.裝載(Loading) (1)說明 (2)雙親委…

《數據結構:單鏈表》

“希望就像星星,或許光芒微弱,但永不熄滅。” 博主的個人gitee:https://gitee.com/friend-a188881041351 一.概念與結構 鏈表是一種物理存儲上非連續、非順序的存儲結構,數據元素的順序邏輯是通過鏈表中的指針鏈接次序實現的。 單…

藍橋杯 - 中等 - 絕美宋詞

介紹 “今宵酒醒何處,楊柳岸曉風殘月”,“驀然回首,那人卻在燈火闌珊處”,“試問閑愁都幾許?一川煙草,滿城風絮,梅子黃時雨” ...... 宋詞可謂是古代文學桂冠上一顆璀璨的明珠,本題…

JDBC、excute()、DriveManager、Connection、Statement、自建JDBC工具類、占位符

DAY19.2 Java核心基礎 JDBC JDBC:Java database Connectivity JDBC是java程序連接各種數據庫的組件 Mybatis就是基于JDBC的封裝,是獨立于數據庫的管理系統,通用的SQL數據庫存取和操作的公共接口 定義了一套標準,為訪問 不同數…

21天Python計劃:函數簡單介紹

文章目錄 前言一、函數知識體系二、函數基礎函數的定義和調用函數參數 三、函數對象、函數嵌套、名稱空間與作用域、裝飾器函數對象函數嵌套名稱空間與作用域裝飾器 四、迭代器、生成器、面向過程編程迭代器生成器面向過程編程 五、三元表達式、列表推導式、生成器表達式、遞歸…

污水處理廠人員定位方案-UWB免布線高精度定位

1. 方案概述 本方案采用免布線UWB基站與北斗衛星定位融合技術,結合UWBGNSS雙模定位工卡,實現污水處理廠室內外人員高精度定位(亞米級)。系統通過低功耗4G傳輸數據,支持實時位置監控、電子圍欄、聚集預警、軌跡回放等功…

無人機DSP處理器工作要點!

一、DSP處理器在無人機中的工作要點 1. 高效運算架構 哈佛結構:DSP采用程序與數據存儲分離的哈佛結構,允許同時訪問指令和數據,提升數據吞吐效率。 流水線技術:將指令分解為取指、譯碼、執行等多個階段并行處理&#xff0c…

MySQL查詢成本計算

對于如上SQL,只是因為查詢字段不同,最終執行時選擇的索引就不同,那么MySQL是如何決定選擇使用哪個索引呢? 答案是MySQL會進行成本計算,對于各個場景查詢進行成本預估,最終選擇最優。 我們可以使用trace工具…

《K230 從熟悉到...》矩形檢測

《K230 從熟悉到...》矩形檢測 《廬山派 K230 從熟悉到...》矩形檢測 矩形檢測技術是一種廣泛應用于電子圖像處理的核心技術。它通過識別和分析圖像中的矩形結構,為各種應用提供基礎支持。從傳統圖像處理算法到現代深度學習技術,矩形檢測的實現途徑多種多…

python基礎學習三(元組及字符串的使用)

文章目錄 元組什么是元組元組的創建方式為什么要將元組設計成不可變序列元組的遍歷集合集合的相關操作集合操作集合的數學操作集合生成式列表,字典,元組,集合總結 字符串字符串的駐留機制判斷字符串的操作方法字符串的比較操作字符串的切片操…

Java基礎-22-基本語法-實體類

實體類(Entity Class) 1. 什么是實體類? 實體類(Entity Class) 是 Java 中用于表示數據庫表結構或業務對象的類。它通常包含屬性(字段)和getter/setter 方法,用于存儲和操作數據。…

Android 系統ContentProvider流程

一、ContentProvider初始化注冊流程 源碼查看路徑:http://xrefandroid.com/android-11.0.0_r48/ 涉及到源碼文件: /frameworks/base/core/java/android/content/ContentProvider.java 自定義ContentProvider需要繼承該類,內部類Transport繼承關系如下,實…

爬蟲工程師分享自動批量化獲取商品評論數據的方法有哪些?

在電商領域,商品評論數據對于商家了解產品口碑、洞悉用戶需求,以及開展競品分析等工作具有極其重要的價值。作為爬蟲工程師,掌握自動批量化獲取商品評論數據的方法,能極大提升數據收集效率。下面,我將分享一些實用的操…

Vue3組件事件用戶信息卡練習

用戶信息卡 題目要求 實現一個用戶信息卡系統&#xff0c;包含以下功能&#xff1a; 1.父組件收集用戶信息&#xff08;姓名、年齡、班級&#xff09; 2.子組件接收并展示用戶信息卡片 3.添加基本的數據驗證 <!DOCTYPE html> <html lang"en"> <h…

SpringBean模塊(二)bean初始化(2)和容器初始化順序的比較--引入ApplicationContextInitializer

前面介紹了獲取容器可以讓spring bean實現ApplicationContextAware&#xff0c;實際也是初始化執行了setApplicationContext接口&#xff0c; 初始化接口還可以借助一些注解或者spring bean的初始化方法&#xff0c;那么他們的執行順序是什么樣的呢&#xff1f; 一、驗證&…

中小型企業網絡的搭建

1.1 網絡邏輯拓撲、布線方案的設計 1.1.1 網絡設計依據 網絡設計應遵循以下基本原則&#xff1a; 高效性&#xff1a;確保網絡架構能夠支持企業日常業務的高效運行。 可靠性&#xff1a;采用冗余設計&#xff0c;確保網絡的高可用性&#xff0c;避免單點故障。 可擴展性…

angr基礎學習

參考&#xff1a;angr AngrCTF_FITM/筆記/03/Angr_CTF從入門到精通&#xff08;三&#xff09;.md at master ZERO-A-ONE/AngrCTF_FITM angr_explore 00_angr_find IDA分析結果&#xff1a; 邏輯簡單&#xff0c;輸入&#xff0c;complex_function進行加密&#xff0c;加密…

軟考-高級-系統架構設計師【考試備考資料下載】

計算機技術與軟件專業技術資格&#xff08;水平&#xff09;考試是原中國計算機軟件專業技術資格和水平考試的完善與發展。計算機技術與軟件專業技術資格&#xff08;水平&#xff09;考試是由國家人力資源和社會保障部、工業和信息化部領導下的國家級考試。 計算機技術與軟件專…

3. 第三放平臺部署deepseek

有時候我們會發現使用deepseek服務器&#xff0c;異常卡頓&#xff0c;這是由于多方面原因造成的&#xff0c;比如說訪問人數過多等。想要解決這個問題&#xff0c;我們可以選擇第三方平臺進行部署 第三方平臺 我們可以選擇的第三方平臺很多&#xff0c;比如硅基流動、秘塔搜索…

1.4-蜜罐\堡壘機\API接口

1.4-蜜罐\堡壘機\API接口 蜜罐&#xff1a;用來釣魚或誘惑測試人員的防護系統 bash <(curl -sS -L https://hfish.net/webinstall.sh) # 安裝HFISH蜜罐堡壘機&#xff1a; 運維用的&#xff0c;統一管理運維平臺;拿下堡壘機就很有可能等于拿下了多個平臺 jumpServer一鍵安…