欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
計算機是如何工作的,Java多線(xiàn)程編程

一、馮諾依曼體系

現代的計算機,大多遵守 馮諾依曼體系結構 (Von Neumann Architecture)


CPU 中央處理器: 進(jìn)行算術(shù)運算和邏輯判斷.

AMD Ryzen 7 580OU with Radeon Graphics
GHz 叫做 CPU 的主頻
這個(gè)數字越大,CPU 就算的越快,表示 1s 執行 32 億條指令

存儲器: 分為外存和內存, 用于存儲數據(使用二進(jìn)制方式存儲)
輸入設備: 用戶(hù)給計算機發(fā)號施令的設備.
輸出設備:計算機個(gè)用戶(hù)匯報結果的設備

針對存儲空間

  • 硬盤(pán) > 內存 >> CPU

針對數據訪(fǎng)問(wèn)速度

  • CPU >> 內存 > 硬盤(pán)

認識計算機的祖師爺 – 馮諾依曼

馮·諾依曼(John von Neumann,1903年12月28日-1957年2月8日), 美籍匈牙利數學(xué)家、計算機科學(xué)家、物理學(xué)家,是20世紀最重要的數學(xué)家之一。馮·諾依曼是布達佩斯大學(xué)數學(xué)博士,在現代計算機、博弈論、核武器和生化武器等領(lǐng)域內的科學(xué)全才之一,被后人稱(chēng)為 “現代計算機之父”, “博弈論之父”.


二、CPU 基本工作流程

1、邏輯門(mén)

1.1、電磁繼電器

電子開(kāi)關(guān) —— 機械繼電器 (Mechanical Relay):
電磁繼電器:通過(guò)通電 / 不通電來(lái)切換開(kāi)關(guān)狀態(tài),得到 1 或者 0 這樣的數據


1.2、門(mén)電路 (Gate Circuit)

基于上述的 “電子開(kāi)關(guān)” 就能構造出基本的門(mén)電路,可以實(shí)現 1 位(bit) 的基本邏輯運算
最基礎的門(mén)電路,有三種:
非門(mén):可以對一個(gè) 0/1 進(jìn)行取反. 0-> 1
與門(mén):可以針對兩個(gè) 0/1 進(jìn)行與運算. 1 0 -> 0
或門(mén):可以針對兩個(gè) 0/1 進(jìn)行或運算. 1 0 -> 1
針對二進(jìn)制數據來(lái)進(jìn)行的.不是"邏輯與”,此處是按位與

借助上述的基礎門(mén)電路,能構造出一個(gè)更復雜的門(mén)電路:異或門(mén)
相同為0,相異為1。1 0 -> 1


1.3、運算器

基于上述的門(mén)電路,還可以搭建出一些運算器

半加器:是針對兩個(gè)比特位,進(jìn)行加法運算


全加器:是針對三個(gè)比特位,進(jìn)行加法運算


1.4、加法器

基于上述的半加器和全加器,就可以構造出一個(gè)針對多個(gè) bit 位的數據進(jìn)行加法運算的加法器了


A和B是兩個(gè)8 bit的數字
A0這個(gè)數字的第0位(最右)A1
A2
A3

電子開(kāi)關(guān)=>基礎的門(mén)電路=>異或門(mén)電路=>半加器=>全加器=>8位加法器

有了加法器之后,就可以計算不只是加法,還能計算減法、乘法、除法都是通過(guò)這個(gè)加法器來(lái)進(jìn)行


2、寄存器,控制單元(CU)

CPU里面除了運算器之外,還有控制單元和寄存器(Register)

門(mén)電路 (電子開(kāi)關(guān))
CPU芯片來(lái)說(shuō),上面就集成了非常非常多的這些電子開(kāi)關(guān),一個(gè)CPU上面的電子開(kāi)關(guān)越多,就認為是計算能力就越強

CPU里面除了運算器之外,還有控制單元寄存器

  • 寄存器是CPU內部用來(lái)存儲數據的組件
    訪(fǎng)問(wèn)速度:寄存器是內存的3-4個(gè)數量級
    存儲空間:比內存小很多很多,現在的x64的cpu (64位的cpu),大概有幾十個(gè)寄存器,每個(gè)寄存器是8個(gè)字節,幾百個(gè)字節,
    成本:CPU上面的這個(gè)寄存器,還是非常貴
    持久化:掉電之后數據丟失

  • 控制單元 CU(Control Unit)
    協(xié)調CPU來(lái)去進(jìn)行工作
    控制單元最主要的工作,能夠去執行指令
    后面進(jìn)行詳細的論述


3、指令(Instruction)

指令和編程密切相關(guān)。

編程語(yǔ)言,大概分成三類(lèi):

1、機器語(yǔ)言
通過(guò)二進(jìn)制的數字,來(lái)表示的不同的操作
不同的CPU (哪怕是同一個(gè)廠(chǎng)商,但是不同型號的CPU),所支持的機器語(yǔ)言(指令)也可能存在差別
2、匯編語(yǔ)言
一個(gè)CPU到底支持哪些指令,生產(chǎn)廠(chǎng)商,會(huì )提供一個(gè)**"芯片手冊”** 詳細介紹CPU都支持哪些指令,每個(gè)指令都是干啥的
匯編語(yǔ)言和機器語(yǔ)言是一對一的關(guān)系 (完全等價(jià))
不同的CPU支持的機器指令不一樣,不同的CPU上面跑的匯編也不一樣
學(xué)校的大部分的匯編語(yǔ)言都是針對一款上古神 U,Intel 8086 CPU
3、高級語(yǔ)言
(C,Java,JS)

指令是如何執行的?
自己構造出一個(gè)最簡(jiǎn)單的芯片手冊:

假設CPU上有兩個(gè)寄存器
A 00
B 01

0010 1010
這個(gè)操作的意思,就是把1010 內存地址上的數據給讀取到A寄存器中
0001 1111
這個(gè)操作的意思,就是把 1111內存地址上的數據讀到寄存器 B
0100 1000
這個(gè)操作的意思,就是把 A寄存器的值,存到 1000這個(gè)內存地址中
1000 0100
這個(gè)操作的意思,就是把 00寄存器和01寄存器的數值進(jìn)行相加,結果放到 00 寄存器里

CPU的工作流程:(通過(guò)CU控制單元來(lái)實(shí)現的)

  1. 從內存中讀取指令
  2. 解析指令
  3. 執行指令

咱們編寫(xiě)的程序,最終都會(huì )被編譯器給翻譯成 CPU 所能識別的機器語(yǔ)言指令,在運行程序的時(shí)候,操作系統把這樣的可執行程序加載到內存中,cpu 就一條一條指令的去進(jìn)行讀取,解析,和執行,如果再搭配上條件跳轉,此時(shí),就能實(shí)現條件語(yǔ)句和循環(huán)語(yǔ)句


三、操作系統(Operating System)

操作系統是一組做計算機資源管理的軟件的統稱(chēng)。目前常見(jiàn)的操作系統有:Windows系列、Unix系列、
Linux系列、OSX系列、Android系列、iOS系列、鴻蒙等

操作系統是一個(gè)搞 "管理的軟件"

  1. 對下,要管理好各種硬件設備
  2. 對上,要給各種軟件提供穩定的運行環(huán)境

1、進(jìn)程/任務(wù)(Process/Task)

exe 可執行文件,都是靜靜的躺在硬盤(pán)上的,在你雙擊之前,這些文件不會(huì )對你的系統有任何影響
但是,一旦你雙擊執行這些 exe 文件,操作系統就會(huì )把這個(gè) exe 給加載到內存中,并且讓 CPU 開(kāi)始執行exe內部的一些指令 (exe里面就存了很多這個(gè)程序對應的二進(jìn)制指令)
這個(gè)時(shí)候,就已經(jīng)把 exe給執行起來(lái),開(kāi)始進(jìn)行了一些具體的工作
這些運行起來(lái)的可執行文件,稱(chēng)為 "進(jìn)程"

這些都是機器上運行的進(jìn)程:

  • 線(xiàn)程:
    線(xiàn)程是進(jìn)程內部的一個(gè)部分, 進(jìn)程包含線(xiàn)程,如果把進(jìn)程想象成是一個(gè)工廠(chǎng),那么線(xiàn)程就是工廠(chǎng)里的生產(chǎn)線(xiàn),一個(gè)工廠(chǎng)里面可以有一個(gè)生產(chǎn)線(xiàn)或者也可以有多個(gè)生產(chǎn)線(xiàn)
    咱們寫(xiě)的代碼,最終的目的都是要跑起來(lái),最終都是要成為一些進(jìn)程
    對于 java 代碼來(lái)說(shuō),最終都是通過(guò) java 進(jìn)程來(lái)跑起來(lái)的 (此處的這個(gè) java 進(jìn)程就是咱們平時(shí)常說(shuō)的jvm)
    進(jìn)程 (process) 還有另一個(gè)名字任務(wù) (task)

2、操作系統是如何管理進(jìn)程的?

  1. 先描述一個(gè)進(jìn)程 (明確出一個(gè)進(jìn)程上面的一些相關(guān)屬性)
  2. 再組織若干個(gè)進(jìn)程 (使用一些數據結構,把很多描述進(jìn)程的信息給放到一起,方便進(jìn)行增刪改查)

描述進(jìn)程:操作系統里面主要都是通過(guò) C/C++來(lái)實(shí)現的,此處的描述其實(shí)就是用的C語(yǔ)言中的 “結構體” (也就和Java的類(lèi)差不多)
**操作系統中描述進(jìn)程的這個(gè)結構體, "PCB" (process control block),進(jìn)程控制塊,這個(gè)東西不是硬件中的那個(gè)PCB板

組織進(jìn)程:典型的實(shí)現,就是使用雙向鏈表來(lái)把每個(gè)進(jìn)程的PCB給串起來(lái)
操作系統的種類(lèi)是很多的,內部的實(shí)現也是各有不同,咱們此處所討論的情況,是以Linux這個(gè)系統為例,由于windows, mac 這樣的系統,不是開(kāi)源的,里面的情況我們并不知道


3、PCB中的一些屬性:

1、pid (進(jìn)程id)
進(jìn)程的身份標識,進(jìn)程的身份證號


2、內存指針
指明了這個(gè)進(jìn)程要執行的代碼 / 指令在內存的哪里,以及這個(gè)進(jìn)程執行中依賴(lài)的數據都在哪里
當運行一個(gè)exe,此時(shí)操作系統就會(huì )把這個(gè) exe 加載到內存中,變成進(jìn)程
進(jìn)程要執行的二進(jìn)制指令 (通過(guò)編譯器生成的), 除了指令之外還有一些重要的數據

3、文件描述符表:
程序運行過(guò)程中,經(jīng)常要和文件打交道 (文件是在硬盤(pán)上的)
文件操作:打開(kāi)文件,讀/寫(xiě)文件,關(guān)閉文件
進(jìn)程每次打開(kāi)一個(gè)文件,就會(huì )在文件描述符表上多增加一項,(個(gè)文件描述符表就可以視為是一個(gè)數組,里面的每個(gè)元素,又是一個(gè)結構體,就對應一個(gè)文件的相關(guān)信息)

一個(gè)進(jìn)程只要一啟動(dòng),不管你代碼中是否寫(xiě)了打開(kāi) / 操作文件的代碼,都會(huì )默認的打開(kāi)三個(gè)文件 (系統自動(dòng)打開(kāi)的),標準輸入(System.in),準輸出(System.out) 標準錯誤(System.err)
要想能讓一個(gè)進(jìn)程正常工作,就需要給這個(gè)進(jìn)程分配一些系統資源:內存,硬盤(pán),CPU

這個(gè)文件描述符表的下標,就稱(chēng)為文件描述符

4、進(jìn)程調度:

  • 上面的屬性是一些基礎的屬性,下面的一組屬性,主要是為了能夠實(shí)現進(jìn)程調度
    進(jìn)程調度:是理解進(jìn)程管理的重要話(huà)題,現在的操作系統,一般都是 “多任務(wù)操作系統”(前身就是 “單任務(wù)操作系統”,同一時(shí)間只能運行一個(gè)進(jìn)程),一個(gè)系統同一時(shí)間,執行了很多的任務(wù)

5、并行和并發(fā):

  • 并行:微觀(guān)上,兩個(gè)CPU核心,同時(shí)執行兩個(gè)任務(wù)的代碼
  • 并發(fā):微觀(guān)上, 一個(gè)CPU核心,先執行一會(huì )任務(wù)1, 再執行一會(huì )任務(wù),再執行一會(huì )任務(wù)…再執行一會(huì )任務(wù)
    只要切換的足夠快, 宏觀(guān)上看起來(lái), 就好像這么多任務(wù)在同時(shí)執行一樣

并行和并發(fā)這兩件事, 只是在微觀(guān)上有區分
宏觀(guān)上咱們區分不了,微觀(guān)上這里的區分都是操作系統自行調度的結果

例如6個(gè)核心,同時(shí)跑20個(gè)任務(wù)
這20個(gè)任務(wù), 有些是并行的關(guān)系, 有些是并發(fā)的關(guān)系??赡苋蝿?wù)A和任務(wù)B,一會(huì )是并行, 一會(huì )是并發(fā)….都是微觀(guān)上操作系統在控制的,在宏觀(guān)上感知不到
正因為在宏觀(guān)上區分不了并行并發(fā), 我們在寫(xiě)代碼的時(shí)候也就不去具體區分這兩個(gè)詞實(shí)際上通常使用 “并發(fā)” 這個(gè)詞, 來(lái)代指并行+并發(fā)
咱們只是在研究操作系統進(jìn)程調度這個(gè)話(huà)題上的時(shí)候, 稍作區分但是其他場(chǎng)景上基本都是使用并發(fā)作為一個(gè)統稱(chēng)來(lái)代替的,并發(fā)編程


4、CPU 分配 —— 進(jìn)程調度(Process Scheduling)

6、調度
所謂的調度就是 “時(shí)間管理”,
并發(fā)就是規劃時(shí)間表的過(guò)程,也就是“調度"的過(guò)程

7、狀態(tài)
狀態(tài)就描述了當前這個(gè)進(jìn)程接下來(lái)應該怎么調度

  • 就緒狀態(tài):隨時(shí)可以去 CPU 上執行
  • 阻塞狀態(tài) / 睡眠狀態(tài):暫時(shí)不可以去CPU上執行

Linux中的進(jìn)程狀態(tài)還有很多其他的…

8、優(yōu)先級
先給誰(shuí)分配時(shí)間,后給誰(shuí)分配時(shí)間,以及給誰(shuí)分的多,給誰(shuí)分的少

9、記賬信息
統計了每個(gè)進(jìn)程,都分別被執行了多久,分別都執行了哪些指令,分別都排隊等了多久了…
給進(jìn)程調度提供指導依據的

10、上下文
就表示了上次進(jìn)程被調度出 CPU 的時(shí)候,當時(shí)程序的執行狀態(tài)。下次進(jìn)程上CPU的時(shí)候,就可以恢復之前的狀態(tài),然后繼續往下執行

進(jìn)程被調度出CPU之前,要先把CPU中的所有的寄存器中的數據都給保存到內存中 (PCB的上下文字段中) ,相當于存檔了
下次進(jìn)程再被調度上CPU的時(shí)候,就可以從剛才的內存中恢復這些數據到寄存器中,相當于讀檔

存檔+讀檔,存檔存儲的游戲信息,就稱(chēng)為 “上下文”


5、內存分配 —— 內存管理(Memory Manage)

進(jìn)程的調度,其實(shí)就是操作系統在考慮CPU資源如何給各個(gè)進(jìn)程分配
那內存資源又是如何分配的呢?

11、虛擬地址空間:
由于操作系統上,同時(shí)運行著(zhù)很多個(gè)進(jìn)程,如果某個(gè)進(jìn)程,出現了bug 進(jìn)程崩潰了,是否會(huì )影響到其他進(jìn)程呢?
現代的操作系統 (windows, linux, mac… ) ,能夠做到這一點(diǎn),就是 “進(jìn)程的獨立性” 來(lái)保證的,就依仗了"虛擬地址空間"

例:如果某個(gè)居民核酸變成陽(yáng)性了,是否會(huì )影響到其他的居民呢?
一旦發(fā)現有人陽(yáng)性了,就需要立刻封樓封小區,否則就會(huì )導致其他人也被傳染,

這個(gè)情況就類(lèi)似于早期的操作系統,早期的操作系統,里面的進(jìn)程都是訪(fǎng)問(wèn)同一個(gè)內存的地址空間。如果某個(gè)進(jìn)程出現 bug,把某個(gè)內存的數據給寫(xiě)錯了,就可能引起其他進(jìn)程的崩潰

解決方案,就是把這個(gè)院子,給劃分出很多的道路
這些道路之間彼此隔離開(kāi),每個(gè)人走各自的道理,這個(gè)時(shí)候就沒(méi)事了,此時(shí)即使有人確診,也影響不到別人了,

如果把進(jìn)程按照虛擬地址空間的方式給劃分出了很多份,這個(gè)時(shí)候不是每一份就只剩一點(diǎn)了嘛?? 雖然你的系統有百八十個(gè)進(jìn)程,但是實(shí)際上從微觀(guān)上看,同時(shí)執行的進(jìn)程,就6個(gè)!!
每個(gè)進(jìn)程能夠撈著(zhù)的內存還是挺多的,而且另一方面,也不是所有的進(jìn)程都用那么多的內存,有的進(jìn)程 (一個(gè)3A游戲,吃幾個(gè)G),大多數的進(jìn)程也就只占幾M即可


6、進(jìn)程間通信(Inter Process Communication)

12、進(jìn)程間通信
進(jìn)程之間現在通過(guò)虛擬地址空間,已經(jīng)各自隔離開(kāi)了,但是在實(shí)際工作中,進(jìn)程之間有的時(shí)候還是需要相互交互的。

例:某業(yè)主A問(wèn):兄弟們,誰(shuí)家有土豆,借我兩個(gè)
業(yè)主B回答:我有土豆,我給你
設定一個(gè)公共空間,這個(gè)空間是任何居民都可以來(lái)訪(fǎng)問(wèn)的,
讓B先把土豆放到公共空間中,進(jìn)行消毒,再讓A來(lái)把這個(gè)公共空間的土豆給取走,彼此就不容易發(fā)生傳染

類(lèi)似的,咱們的兩個(gè)進(jìn)程之間,也是隔離開(kāi)的,也是不能直接交互的,操作系統也是提供了類(lèi)似的 "公共空間”,
進(jìn)程 A 就可以把數據見(jiàn)放到公共空間上,進(jìn)程B再取走

進(jìn)程間通信:
操作系統中,提供的 “公共空間” 有很多種,并且各有特點(diǎn),有的存儲空間大,有的小,有的速度快,有的慢.….
操作系統中提供了多種這樣的進(jìn)程間通信機制,(有些機制是屬于歷史遺留的,已經(jīng)不適合于現代的程序開(kāi)發(fā))

現在最主要使用的進(jìn)程間通信方式兩種:
1.文件操作
2.網(wǎng)絡(luò )操作 (socket)

總結:


四、多線(xiàn)程

1、線(xiàn)程(Thread)

為啥要有進(jìn)程?因為我們的系統支持多任務(wù)了,程序猿也就需要 “并發(fā)編程”
通過(guò)多進(jìn)程,是完全可以實(shí)現并發(fā)編程的,但是有點(diǎn)小問(wèn)題:
如果需要頻繁的創(chuàng )建而 / 銷(xiāo)毀進(jìn)程,這個(gè)事情成本是比較高的,如果需要頻繁的調度進(jìn)程,這個(gè)事情成本也是比較高的:
對于資源的申請和放,本身就是一個(gè)比較低效的操作,

創(chuàng )建進(jìn)程就得分配資源:
1)內存
2)文件
銷(xiāo)毀進(jìn)程也得釋放資源
1)內存
2)文件

如何解決這個(gè)問(wèn)題?思路有兩個(gè):

  1. 進(jìn)程池: (如數據庫連接池,字符串常量池)
    進(jìn)程池雖然能解決上述問(wèn)題,提高效率。同時(shí)也有問(wèn)題:池子里的閑置進(jìn)程,不使用的時(shí)候也在消耗系統資源,消耗的系統資源太多了
  2. 使用線(xiàn)程來(lái)實(shí)現并發(fā)編程:
    線(xiàn)程比進(jìn)程更輕量,每個(gè)進(jìn)程可以執行一個(gè)任務(wù),每個(gè)線(xiàn)程也能執行一個(gè)任務(wù) (執行一段代碼),也能夠并發(fā)編程,
    創(chuàng )建線(xiàn)程的成本比創(chuàng )建進(jìn)程要低很多。銷(xiāo)毀線(xiàn)程,的成本也比銷(xiāo)毀進(jìn)程低很多。調度線(xiàn)程,的成本也比調度進(jìn)程低很多。
    Linux 上也把線(xiàn)程稱(chēng)為輕量級進(jìn)程(LWP light weight process)

2、為什么線(xiàn)程比進(jìn)程更輕量?

  1. 進(jìn)程重量是重在哪里:重在資源申請釋放 (在倉庫里找東西…)
  2. 線(xiàn)程是包含在進(jìn)程中的,一個(gè)進(jìn)程中的多個(gè)線(xiàn)程,共用同一份資源 (同一份內存+文件)
    只是創(chuàng )建進(jìn)程的第一個(gè)線(xiàn)程的時(shí)候 (由于要分配資源)成本是相對高的,后續這個(gè)進(jìn)程中再創(chuàng )建其他線(xiàn)程,這個(gè)時(shí)候成本都是要更低一些,所以為什么更輕量?少了申請釋放資源的過(guò)程

可以把進(jìn)程比作一個(gè)工廠(chǎng),假設這個(gè)工廠(chǎng)有一些生產(chǎn)任務(wù),例如要生產(chǎn) 1w 部手機
要想提高生產(chǎn)效率:
1). 搞兩個(gè)工廠(chǎng),一個(gè)生產(chǎn) 5k (多創(chuàng )建了一個(gè)進(jìn)程)
2). 還是一個(gè)工廠(chǎng),在一個(gè)工廠(chǎng)里多加一個(gè)生產(chǎn)線(xiàn),兩個(gè)生產(chǎn)線(xiàn)并行生產(chǎn),一個(gè)生產(chǎn)線(xiàn)生產(chǎn)5k,(多創(chuàng )建了一個(gè)線(xiàn)程)
最終生產(chǎn)1w個(gè)手機,花的時(shí)間差不多,但是這里的成本就不一樣了

多加一些線(xiàn)程,是不是效率就會(huì )進(jìn)一步提高呢?一般來(lái)說(shuō)是會(huì ),但是也不一定
如果線(xiàn)程多了,這些線(xiàn)程可能要競爭同一個(gè)資源,這個(gè)時(shí)候,整體的速度就收到了限制,整體硬件資源是有限的

總結進(jìn)程與線(xiàn)程的區別:

  1. 進(jìn)程包含線(xiàn)程,一個(gè)進(jìn)程里可以有一個(gè)線(xiàn)程,也可以有多個(gè)線(xiàn)程
  2. 進(jìn)程和線(xiàn)程都是為了處理 并發(fā)編程 這樣的場(chǎng)景
    但是進(jìn)程有問(wèn)題,頻繁創(chuàng )建和釋放的時(shí)候效率低,相比之下,線(xiàn)程更輕量,創(chuàng )建和釋放效率更高。為啥更輕量?少了申請釋放資源的過(guò)程
  3. 操作系統創(chuàng )建進(jìn)程,要給進(jìn)程分配資源,進(jìn)程是操作系統分配資源的基本單位
    操作系統創(chuàng )建的線(xiàn)程,是要在 CPU上調度執行,線(xiàn)程是操作系統調度執行的基本單位,(前面講的時(shí)間管理,當時(shí)咱們是調度的進(jìn)程,但是更準確的說(shuō),其實(shí)是調度的線(xiàn)程
    • 調度的進(jìn)程:前面的例子相當于是每個(gè)進(jìn)程里,只有一個(gè)線(xiàn)程了,可以視為是在調度進(jìn)程,但是如果進(jìn)程里有多個(gè)線(xiàn)程,更嚴謹的說(shuō)法,還是以線(xiàn)程為單位進(jìn)行調度
  4. 進(jìn)程具有獨立性,每個(gè)進(jìn)程有各自的虛擬地址空間,一個(gè)進(jìn)程掛了,不會(huì )影響到其他進(jìn)程。
    同一個(gè)進(jìn)程中的多個(gè)線(xiàn)程,共用同一個(gè)內存空間,一個(gè)線(xiàn)程掛了,可能影響到其他線(xiàn)程的,甚至導致整個(gè)進(jìn)程崩潰

Java這個(gè)生態(tài)中更常使用的并發(fā)編程方式,是多線(xiàn)程
其他的語(yǔ)言,主打的并發(fā)變成又不一樣:
go,主要是通過(guò)多協(xié)程的方式實(shí)現并發(fā).
erlang,這個(gè)是通過(guò) actor 模型實(shí)現并發(fā).
JS,是通過(guò)定時(shí)器+事件回調的方式實(shí)現并發(fā).……
多線(xiàn)程仍然是最主流最常見(jiàn)的一種并發(fā)編程的方式


五、Java 多線(xiàn)程編程

Java 的線(xiàn)程 和 操作系統線(xiàn)程 的關(guān)系:

  1. 線(xiàn)程是操作系統中的概念,操作系統內核實(shí)現了線(xiàn)程這樣的機制,并且對用戶(hù)層提供了一些 API 供用戶(hù)使用 (例如 Linux 的 pthread 庫),
    在 Java 標準庫 中,就提供了一個(gè) Thread 類(lèi),來(lái)表示 / 操作線(xiàn)程,Thread 類(lèi)可以視為 Java 標準庫提供的 API, 對操作系統提供的 API 進(jìn)行了進(jìn)一步的抽象和封裝
  2. 創(chuàng )建好的 Thread實(shí)例,其實(shí)和操作系統中的線(xiàn)程是一 一對應的關(guān)系,操作系統提供了一組關(guān)于線(xiàn)程的API(C語(yǔ)言風(fēng)格),Java對于這組API進(jìn)一步封裝了,就成了Thread 類(lèi)

1、第一個(gè)多線(xiàn)程程序

Thread類(lèi)的基本用法
通過(guò) Thread 類(lèi)創(chuàng )建線(xiàn)程,寫(xiě)法有很多種
其中最簡(jiǎn)單的做法,創(chuàng )建子類(lèi),繼承自Thread,并且重寫(xiě) run 方法

package thread;

class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("hello thread!");;
    }
}

public class demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.start();
    }
}

run 方法描述了,這個(gè)線(xiàn)程內部要執行哪些代碼,每個(gè)線(xiàn)程都是并發(fā)執行的 (各自執行各自的代碼),因此就需要告知這個(gè)線(xiàn)程,你執行的代碼是什么,
run 方法中的邏輯,是在新創(chuàng )建出來(lái)的線(xiàn)程中,被執行的代碼

并不是我一定義這個(gè)類(lèi),一寫(xiě)run方法,線(xiàn)程就創(chuàng )建出來(lái),相當于我把活安排出來(lái)了,但是同學(xué)們還沒(méi)開(kāi)始干呢
需要調用這里的 start 方法,才是真正的在系統中創(chuàng )建了線(xiàn)程,才是真正開(kāi)始執行上面的 run 操作,在調用 start 之前,系統中是沒(méi)有創(chuàng )建出線(xiàn)程的


2、線(xiàn)程之間是并發(fā)執行的

如果在一個(gè)循環(huán)中不加任何限制,這個(gè)循環(huán)轉的速度非常非???,導致打印的東西太多了,根本看不過(guò)來(lái)了,就可以加上一個(gè) sleep 操作,來(lái)強制讓這個(gè)線(xiàn)程休眠一段時(shí)間
這個(gè)休眠操作,就是強制地讓線(xiàn)程進(jìn)入阻塞狀態(tài),單位是 ms,就是1s 之內這個(gè)線(xiàn)程不會(huì )到 cpu 上執行

public void run() {
   while (true) {
        System.out.println("hello thread!");
        Thread.sleep(1000);
    }
}

這是多線(xiàn)程編程中最常見(jiàn)的一個(gè)異常,線(xiàn)程被強制的中斷了,用 try catch 處理

在一個(gè)進(jìn)程中,至少會(huì )有一個(gè)線(xiàn)程,
在一個(gè) java進(jìn)程中,也是至少會(huì )有一個(gè)調用 main 方法的線(xiàn)程 (這個(gè)線(xiàn)程不是你手動(dòng)搞出來(lái)的)
自己創(chuàng )建的 t 線(xiàn)程 和 自動(dòng)創(chuàng )建的 main 線(xiàn)程,就是并發(fā)執行的關(guān)系 (宏觀(guān)上看起來(lái)是同時(shí)執行)
此處的并發(fā) = 并行 + 并發(fā)
宏觀(guān)上是區分不了并行和并發(fā)的,都取決于系統內部的調度

package thread;

// Thread是在java.lang 里的,java.lang 里的類(lèi)都不需要手動(dòng)導入,類(lèi)似的還有String
class MyThread2 extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello thread!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class demo2 {
    public static void main(String[] args) {
        Thread t = new MyThread2();
        t.start();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

運行打?。?/strong>

/* hello main
hello thread!
hello thread!
hello main
hello main
hello thread!
hello thread!
hello main */

現在兩個(gè)線(xiàn)程,都是打印一條,就休眠 1s
當1s 時(shí)間到了之后,系統先喚醒誰(shuí)呢?
看起來(lái)這個(gè)順序不是完全確定 (隨機的)
每一輪,1s 時(shí)間到了之后,到底是先喚醒 main 還是 thread,這是不確定的 (隨機的)
操作系統來(lái)說(shuō),內部對于線(xiàn)程之間的調度順序,在宏觀(guān)上可以認為是隨機的 (搶占式執行)
這個(gè)隨機性,會(huì )給多線(xiàn)程編程帶來(lái)很多其他的麻煩


3、Thread 類(lèi)創(chuàng )建線(xiàn)程的寫(xiě)法

寫(xiě)法一: 創(chuàng )建子類(lèi),繼承自 Thread

寫(xiě)法二: 創(chuàng )建一個(gè)類(lèi),實(shí)現 Runnable 接口,再創(chuàng )建 Runnable實(shí)例傳給Thread 實(shí)例

通過(guò) Runnable 來(lái)描述任務(wù)的內容
進(jìn)—步的再把描述好的任務(wù)交給Thread 實(shí)例

package thread;

// Runnable 就是在描述一個(gè)任務(wù)
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("hello");
    }
}

public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
    }
}

寫(xiě)法三 / 寫(xiě)法四: 就是上面兩個(gè)寫(xiě)法的翻版,使用了匿名內部類(lèi)

創(chuàng )建了一個(gè)匿名內部類(lèi),繼承自 Thread 類(lèi),同時(shí)重寫(xiě)run方法,同時(shí)再new出這個(gè)匿名內部類(lèi)的實(shí)例

package thread;

public class demo4 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                System.out.println("hello thread!");
            }
        };
        t.start();
    }
}

new 的是Runnable,針對這個(gè)創(chuàng )建的匿名內部類(lèi),同時(shí)new 出的 Runnable` 實(shí)例傳給 Thread 的構造方法

package thread;

public class demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello thread!");
            }
        });
        t.start();
    }
}

通常認為Runnable 這種寫(xiě)法更好一點(diǎn),能夠做到讓線(xiàn)程和線(xiàn)程執行的任務(wù),更好的進(jìn)行解耦
寫(xiě)代碼一般希望,高內聚,低耦合
Runnable 單純的只是描述了一個(gè)任務(wù),至于這個(gè)任務(wù)是要通過(guò)一個(gè)進(jìn)程來(lái)執行,還是線(xiàn)程來(lái)執行,還是線(xiàn)程池來(lái)執行,還是協(xié)程來(lái)執行,Runnable 本身并不關(guān)心,Runnable 里面的代碼也不關(guān)心

第五種寫(xiě)法: 相當于是第四種寫(xiě)法的延伸,使用 lambda 表達式,是使用lambda 代替了 Runnable 而已

package thread;

public class demo6 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello thread!");
        });
        t.start();
    }
}

多線(xiàn)程能夠提高任務(wù)完成的效率

測試:有兩個(gè)整數變量,分別要對這倆變量自增10億次,分別使用一個(gè)線(xiàn)程,和兩個(gè)線(xiàn)程

此處不能直接這么記錄結束時(shí)間,別忘了,現 在這個(gè)求時(shí)間戳的代碼是在 main 線(xiàn)程中
maint1 ,t2 之間是并發(fā)執行的關(guān)系,此處t1t2 還沒(méi)執行完呢,這里就開(kāi)始記錄結束時(shí)間了,這顯然是不準確的
正確做法應該是main線(xiàn)程等待 t1t2 跑完了,再來(lái)記錄結束時(shí)間
join 效果就是等待線(xiàn)程結束,t1.join就是讓main 線(xiàn)程等待t1 結束,t2.joinmain 線(xiàn)程等待 t2結束

package thread;

public class demo7 {
    private static final long count = 10_0000_0000;

    public static void serial() {
        long begin = System.currentTimeMillis();
        long a = 0;
        for (int i = 0; i < count; i++) {
            a++;
        }
        long b = 0;
        for (int i = 0; i < count; i++) {
            b++;
        }
        long end = System.currentTimeMillis();
        System.out.println("消耗時(shí)間: " + (end- begin) + "ms");
    }

    public static void concurrency() throws InterruptedException {
        long begin = System.currentTimeMillis();
        Thread t1 = new Thread(() -> {
            long a = 0;
            for (int i = 0; i < count; i++) {
                a++;
            }
        });
        t1.start();
        Thread t2 = new Thread(() -> {
            long b = 0;
            for (int i = 0; i < count; i++) {
                b++;
            }
        });
        t2.start();

        t1.join(); // 讓 main 線(xiàn)程等待 t1 結束
        t2.join(); // 讓 main 線(xiàn)程等待 t2 結束

        long end = System.currentTimeMillis();
        System.out.println("消耗時(shí)間: " + (end- begin) + "ms");
    }

    public static void main(String[] args) throws InterruptedException {
        // serial();
        concurrency();
    }
}

串行執行的時(shí)候,時(shí)間大概是600多ms (平均650左右)
兩個(gè)線(xiàn)程并發(fā)執行,時(shí)間大概是400多ms (平均450左右)
提升了接近50%
并不是說(shuō),一個(gè)線(xiàn)程600多ms,兩個(gè)線(xiàn)程就是300多ms
這倆線(xiàn)程在底層到底是并行執行,還是并發(fā)執行,不確定,真正并行執行的時(shí)候,效率才會(huì )有顯著(zhù)提升

多線(xiàn)程特別適合于那種CPU密集型的程序,程序要進(jìn)行大量的計算,使用多線(xiàn)程就可以更充分的利用CPU的多核資源


六、Thread類(lèi)的其他的屬性和方法

1、Thread 的常見(jiàn)構造方法

方法說(shuō)明
Thread()創(chuàng )建線(xiàn)程對象
Thread(Runnable target)使用 Runnable 對象創(chuàng )建線(xiàn)程對象
Thread(String name)創(chuàng )建線(xiàn)程對象,并命名
Thread(Runnable target, String name)使用 Runnable 對象創(chuàng )建線(xiàn)程對象,并命名
【了解】Thread(ThreadGroup group, Runnable target)線(xiàn)程可以被用來(lái)分組管理,分好的組即為線(xiàn)程組,這 個(gè)目前我們了解即可

Thread(String name):這個(gè)東西是給線(xiàn)程 (thread對象) 起一個(gè)名字,起一個(gè)啥樣的名字,不影響線(xiàn)程本身的執行
僅僅只是影響到程序猿調試,可以借助一些工具看到每個(gè)線(xiàn)程以及名字,很容易在調試中對線(xiàn)程做出區分

可以使用 jconsole 來(lái)觀(guān)察線(xiàn)程的名字,jconsolejdk自帶的一個(gè)調試工具


jconsole 這里能夠羅列出你系統上的java進(jìn)程(其他進(jìn)程不行)




2、Thread 的幾個(gè)常見(jiàn)屬性

屬性獲取方法
IDgetId()
名稱(chēng)getName()
狀態(tài)getState()
優(yōu)先級getPriority()
是否后臺線(xiàn)程isDaemon()
是否存活isAlive()
是否被中斷isInterrupted(

是否后臺線(xiàn)程isDaemon()
如果線(xiàn)程是后臺線(xiàn)程,不影響進(jìn)程退出
如果線(xiàn)程不是后臺線(xiàn)程 (前臺線(xiàn)程),就會(huì )影響到進(jìn)程退出
創(chuàng )建的 t1t2 默認都是前臺的線(xiàn)程
即使 main 方法執行完畢,進(jìn)程也不能退出,得等 t1t2 都執行完,整個(gè)進(jìn)程才能退出!
如果 t1t2 是后臺線(xiàn)程,此時(shí)如果 main 執行完畢,整個(gè)進(jìn)程就直接退出,t1t2 就被強行終止了

是否存活isAlive()
操作系統中對應的線(xiàn)程是否正在運行
Thread t 對象的生命周期和內核中對應的線(xiàn)程,生命周期并不完全一致
創(chuàng )建出t對象之后,在調用 start 之前,系統中是沒(méi)有對應線(xiàn)程的
run方法執行完了之后,系統中的線(xiàn)程就銷(xiāo)毀了,但是t這個(gè)對象可能還存在,通過(guò) isAlive就能判定當前系統的線(xiàn)程的運行情況
如果調用 start之后,run執行完之前,isAlive 就是返回true 。如果調用start 之前,run執行完之后,isAlive 就返回 false

ID是線(xiàn)程的唯一標識,不同線(xiàn)程不會(huì )重復
名稱(chēng)是各種調試工具用到
優(yōu)先級高的線(xiàn)程理論上來(lái)說(shuō)更容易被調度到
關(guān)于后臺線(xiàn)程,需要記住一點(diǎn):JVM會(huì )在一個(gè)進(jìn)程的所有非后臺線(xiàn)程結束后,才會(huì )結束運行。
是否存活,即簡(jiǎn)單的理解,為 run 方法是否運行結束了

public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我還活著(zhù)");
                            Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即將死去");
        });
        System.out.println(Thread.currentThread().getName()
                + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName()
                + ": 名稱(chēng): " + thread.getName());
        System.out.println(Thread.currentThread().getName()
                + ": 狀態(tài): " + thread.getState());
        System.out.println(Thread.currentThread().getName()
                + ": 優(yōu)先級: " + thread.getPriority());
        System.out.println(Thread.currentThread().getName()
                + ": 后臺線(xiàn)程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                + ": 活著(zhù): " + thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                + ": 被中斷: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 狀態(tài): " + thread.getState());
    }
}

3、Thread中的一些重要方法

3.1、啟動(dòng)一個(gè)線(xiàn)程-start()

start() 決定了系統中是不是真的創(chuàng )建出線(xiàn)程

start 和 run 的區別:

  • run() 單純的只是一個(gè)普通的方法,描述了任務(wù)的內容
  • start() 則是一個(gè)特殊的方法,內部會(huì )在系統中創(chuàng )建線(xiàn)程
package thread;

public class demo1 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        // t.run();

        while (true) {
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

start() 是并發(fā)執行,而 run()循環(huán)打印 hello thread
run方法只是一個(gè)普通的方法,你在main線(xiàn)程里調用run,其實(shí)并沒(méi)有創(chuàng )建新的線(xiàn)程,這個(gè)循環(huán)仍然是在 main 線(xiàn)程中執行的
既然是在一個(gè)線(xiàn)程中執行,代碼就得從前到后的按順序運行,運行第一個(gè)循環(huán),再運行第二個(gè)循環(huán)

for (int i = 0; i < 5; i++) {
	System.out.println("hello thread");
		try {
			Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
}

如果改成循環(huán)五次,打印五個(gè) hello thread,讓后打印 hello main


3.2、中斷一個(gè)線(xiàn)程

中斷線(xiàn)程:讓一個(gè)線(xiàn)程停下來(lái)

線(xiàn)程停下來(lái)的關(guān)鍵,是要讓線(xiàn)程對應的 run方法執行完
還有一個(gè)特殊的是 main 這個(gè)線(xiàn)程,對于main 來(lái)說(shuō),得是main方法執行完,線(xiàn)程就完了)

如何中斷線(xiàn)程:

1、標志位
可以手動(dòng)的設置一個(gè)標志位 (自己創(chuàng )建的變量,boolean),來(lái)控制線(xiàn)程是否要執行結束

package thread;

public class demo2 {
    private static boolean isQuit = false;

    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!isQuit) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        // 只要把這個(gè) isQuit 設為true,這個(gè)循環(huán)就退出了,進(jìn)一步的 run 就執行完了,再進(jìn)一步就是線(xiàn)程執行結束了
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        isQuit = true;
        System.out.println("終止 t 線(xiàn)程!");
    }
}

運行輸出:

hello thread
hello thread
hello thread
hello thread
hello thread
終止 t 線(xiàn)程!

在其他線(xiàn)程中控制這個(gè)標志位,就能影響到這個(gè)線(xiàn)程的結束
此處因為,多個(gè)線(xiàn)程共用同一個(gè)虛擬地址空間,因此,main 線(xiàn)程修改的 isQuitt 線(xiàn)程判定的 isQuit,是同一個(gè)值


2、interrupted()
但是,isQuit 并不嚴謹,更好的做法,使用 Thread 中內置的一個(gè)標志位來(lái)進(jìn)行判定
Thread.interrupted() 這是一個(gè)靜態(tài)的方法
Thread.currentThread().isInterrupted() 這是實(shí)例方法,其中 currentThread 能夠獲取到當前線(xiàn)程的實(shí)例

package thread;

public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在主線(xiàn)程中,調用 interrupt 方法,中斷這個(gè)線(xiàn)程
        // 讓 t 線(xiàn)程被中斷
        t.interrupt();
    }
}

運行此代碼,打印五次 hello thread 后,出現異常,然后繼續打印 hello thread

調用 t.interrupt() 這個(gè)方法,可能產(chǎn)生兩種情況:
1). 如果 t 線(xiàn)程是處在就緒狀態(tài),就是設置線(xiàn)程的標志位為 true
2). 如果 t 線(xiàn)程處在阻塞狀態(tài) (sleep 休眠了),就會(huì )觸發(fā)一個(gè) InterruptException

這個(gè)代碼絕大部分情況,都是在休眠狀態(tài)阻塞
此處的中斷,是希望能夠立即產(chǎn)生效果的
如果線(xiàn)程已經(jīng)是阻塞狀態(tài)下,此時(shí)設置標志位就不能起到及時(shí)喚醒的效果

調用這個(gè) interrupt 方法,就會(huì )讓 sleep 觸發(fā)一個(gè)異常,從而導致線(xiàn)程從阻塞狀態(tài)被喚醒
當下的代碼,一旦觸發(fā)了異常之后,就進(jìn)入了catch 語(yǔ)句,在catch 中,就單純的只是打了一個(gè)日志
printStackTrace 是打印當前出現異常位置的代碼調用棧,打完日志之后,就直接繼續運行

解決方法:

package thread;

public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 當前觸發(fā)異常后,立即退出循環(huán)
                    System.out.println("這個(gè)是收尾工作");
                    break;
                }
            }
        });
        t.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 在主線(xiàn)程中,調用 interrupt 方法,中斷這個(gè)線(xiàn)程
        // 讓 t 線(xiàn)程被中斷
        t.interrupt();
    }
}

運行結果:

hello thread
hello thread
hello thread
hello thread
hello thread
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at thread.demo3.lambda$main$0(demo3.java:9)
	at java.lang.Thread.run(Thread.java:748)
這個(gè)是收尾工作

推薦的做法:
咱們一個(gè)代碼中的線(xiàn)程有很多個(gè),隨時(shí)哪個(gè)線(xiàn)程都可能會(huì )終止
Thread.interrupted() 這個(gè)方法判定的標志位是Threadstatic成員,一個(gè)程序中只有一個(gè)標志位
Thread.currentThread().isInterrupted()這個(gè)方法判定的標志位是 Thread的普通成員,每個(gè)示例都有自己的標志位,一般就無(wú)腦使用這個(gè)方法即可

方法說(shuō)明
public void interrupt()中斷對象關(guān)聯(lián)的線(xiàn)程,如果線(xiàn)程正在阻塞,則以異常方式通知, 否則設置標志位
public static boolean interrupted()判斷當前線(xiàn)程的中斷標志位是否設置,調用后清除標志位
public boolean isInterrupted()判斷對象關(guān)聯(lián)的線(xiàn)程的標志位是否設置,調用后不清除標志位

3.3、線(xiàn)程等待-join()

多個(gè)線(xiàn)程之間,調度順序是不確定的,線(xiàn)程之間的執行是按照調度器來(lái)安排的,這個(gè)過(guò)程可以視為是 “無(wú)序,隨機”,這樣不太好,有些時(shí)候,我們需要能夠控制線(xiàn)程之間的順序

線(xiàn)程等待,就是其中一種控制線(xiàn)程執行順序的手段
此處的線(xiàn)程等待,主要是控制線(xiàn)程結束的先后順序

join():調用 join 的時(shí)候,哪個(gè)線(xiàn)程調用的 join ,哪個(gè)線(xiàn)程就會(huì )阻塞等待,要等到對應的線(xiàn)程執行完畢為止 (對應線(xiàn)程的 run 執行完)

package thread;

public class demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        // 在主線(xiàn)程中,使用等待操作,等 t 線(xiàn)程執行結束
        try {
            t.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

首先,調用這個(gè)方法的線(xiàn)程是 main 線(xiàn)程,針對t這個(gè)線(xiàn)程對象調用的,此時(shí)就是讓 main 等待t
調用 join 之后,main 線(xiàn)程就會(huì )進(jìn)入阻塞狀態(tài) (暫時(shí)無(wú)法在cpu上執行)
代碼執行到 join` 這一行,就暫時(shí)停下了,不繼續往下執行了

那么join什么時(shí)候能繼續往下走,恢復成就緒狀態(tài)呢?
就是等到 t 線(xiàn)程執行完畢 ( trun方法跑完了)
通過(guò)線(xiàn)程等待,就是在**控制讓** t先結束,main 后結束,一定程度上的干預了這兩個(gè)線(xiàn)程的執行順序
這是代碼中控制的先后順序,就像剛才寫(xiě)的自增 100 億次這個(gè)代碼,計時(shí)操作就是要在計算線(xiàn)程執行完之后再執行

但是 join 操作默認情況下,是死等,不見(jiàn)不散,這不合理
join 提供了另外一個(gè)版本,就是可以設置等待時(shí)間,最長(cháng)等待多久,如果等不到,就不等了

try {
	t.join(10000);
} catch (InterruptedException e) {
	e.printStackTrace();
}

進(jìn)入 join 也會(huì )產(chǎn)生阻塞,這個(gè)阻塞不會(huì )一直持續下去,如果 10s 之內,t線(xiàn)程結束了,此時(shí) join直接返回
如果10s之后,t 仍然不結束, 此時(shí)join 也就直接返回
日常開(kāi)發(fā)中涉及到的一些 "等待” 相關(guān)的操作,一般都不會(huì )是死等,而是會(huì )有這樣的 "超時(shí)時(shí)間"


3.4、獲取當前線(xiàn)程的引用

Thread.currentThread() 就能夠獲取到當前線(xiàn)程的引用 (Thread 實(shí)例的引用),哪個(gè)線(xiàn)程調用的這個(gè)currentThread,就獲取到的是哪個(gè)線(xiàn)程的實(shí)例

package thread;

public class demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()); // Thread-0
            }
        };
        t.start();

        // 在 main 線(xiàn)程中調用的,拿到的就是 main 這個(gè)線(xiàn)程的實(shí)例
        System.out.println(Thread.currentThread().getName()); // main
    }
}

this.getName() :對于這個(gè)代碼來(lái),是通過(guò)繼承 Thread 的方式來(lái)創(chuàng )建線(xiàn)程
此時(shí)在 run 方法中,直接通過(guò) this,拿到的就是當前 Thread 的實(shí)例

		Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println(this.getName());
            }
        };
        t.start();

此處的 this 不是指向 Thread 類(lèi)型了,而是指向 Runnable,而 Runnable 只是一個(gè)單純的任務(wù),沒(méi)有 name 屬性的

		Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(this.getName()); // err
            }
        });
        t.start();

要想拿到線(xiàn)程的名字,只能通過(guò) Thread.currentThread()
lambda 表達式效果同 Runnable

		Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        t.start();

3.5、休眠當前線(xiàn)程

sleep 所謂的休眠到底是在干啥?
進(jìn)程:PCB+雙向鏈表,這個(gè)說(shuō)法是針對只有一個(gè)線(xiàn)程的進(jìn)程是如此的
如果是一個(gè)進(jìn)程有多個(gè)線(xiàn)程,此時(shí)每個(gè)線(xiàn)程都有一個(gè)PCB,一個(gè)進(jìn)程對應的就是一組PCB了
PCB 上有一個(gè)字段tgroupld,這個(gè)id其實(shí)就相當于進(jìn)程的id,同一個(gè)進(jìn)程中的若干個(gè)線(xiàn)程的 tgroupld 是相同的

process control block
進(jìn)程控制塊 和 線(xiàn)程有啥關(guān)系?其實(shí) Linux內核不區分進(jìn)程和線(xiàn)程
進(jìn)程線(xiàn)程是程序猿寫(xiě)應用程序代碼,搞出來(lái)的詞,實(shí)際上 Linux內核只認PCB !
在內核里 Linux 把線(xiàn)程稱(chēng)為輕量級進(jìn)程


如果某個(gè)線(xiàn)程調用了sleep 方法,這個(gè) PCB 就會(huì )進(jìn)入到阻塞隊列

操作系統調度線(xiàn)程的時(shí)候,就只是從就緒隊列中挑選合適的 PCBCPU 上運行,阻塞隊列里的 PCB 就只能干等著(zhù),當睡眠時(shí)間到了,系統就會(huì )把剛才這個(gè) PCB 從阻塞隊列挪回到就緒隊列,以上情況都是在 Linux 系統

內核中的很多工作都依賴(lài)大量的數據結構,但凡是需要管理很多數據的程序,都大量的依賴(lài)數據結構


4、線(xiàn)程的狀態(tài)

進(jìn)程有狀態(tài):就緒,阻塞
這里的狀態(tài)就決定了系統按照啥樣的態(tài)度來(lái)調度這個(gè)進(jìn)程,這里相當于是針對一個(gè)進(jìn)程中只有一個(gè)線(xiàn)程的情況
更常見(jiàn)的情況下,一個(gè)進(jìn)程中包含了多個(gè)線(xiàn)程,所謂的狀態(tài),其實(shí)是綁定在線(xiàn)程上
Linux 中,PCB 其實(shí)是和線(xiàn)程對應的,一個(gè)進(jìn)程對應著(zhù)一組 PCB

上面說(shuō)的 “就緒" 和 “阻塞” 都是針對系統層面上的線(xiàn)程的狀態(tài) (PCB)
在 Java 中Thread 類(lèi)中,對于線(xiàn)程的狀態(tài),又進(jìn)—步的細化了

1、 NEW:安排了工作, 還未開(kāi)始行動(dòng)

把 Thread 對象創(chuàng )建好了,但是還沒(méi)有調用 start

public class demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            
        });
        System.out.println(t.getState()); // NEW
        t.start();
    }
}

2、 TERMINATED:工作完成了

操作系統中的線(xiàn)程已經(jīng)執行完畢,銷(xiāo)毀了但是 Thread 對象還在,獲取到的狀態(tài)

public class demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {

        });
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState()); // TERMINATED
    }
}

以上兩個(gè)狀態(tài)是 Java 內部搞出來(lái)的狀態(tài),就和操作系統中的 PCB 里的狀態(tài)就沒(méi)啥關(guān)系

3、 RUNNABLE:可工作的,又可以分成正在工作中和即將開(kāi)始工作

就緒狀態(tài),處于這個(gè)狀態(tài)的線(xiàn)程,就是在就緒隊列中,隨時(shí)可以被調度到 CPU 上
如果代碼中沒(méi)有進(jìn)行 sleep,也沒(méi)有進(jìn)行其他的可能導致阻塞的操作,代碼大概率是處在 Runnable 狀態(tài)的

public class demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                // 這里什么都不能有
            }
        });
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState()); // RUNNABLE
    }
}

一直持續不斷的執行這里的循環(huán),隨時(shí)系統想調度它上cpu都是隨時(shí)可以的

4、TIMED_WAITING:這幾個(gè)都表示排隊等著(zhù)其他事情

代碼中調用了sleep,就會(huì )進(jìn)入到 TIMED_WAITIN,意思就是當前的線(xiàn)程在一定時(shí)間之內,是阻塞的狀態(tài)

public class demo7 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        Thread.sleep(1000);
        System.out.println(t.getState()); // TIMED_WAITING
    }
}

一定時(shí)間到了之后,阻塞狀態(tài)解除這種情況就是 TIMED_WAITING,也是屬于阻塞的狀態(tài)之一

5、BLOCKED:這幾個(gè)都表示排隊等著(zhù)其他事情

當前線(xiàn)程在等待鎖,導致了阻塞(阻塞狀態(tài)之一) --synchronized

6、WAITING:這幾個(gè)都表示排隊等著(zhù)其他事情

當前線(xiàn)程在等待喚醒,導致了阻塞(阻塞狀態(tài)之一) --wait

為啥要這么細分?這是非常有好處的:
開(kāi)發(fā)過(guò)程中經(jīng)常會(huì )遇到一種情況,程序 "卡死” 了
一些關(guān)鍵的線(xiàn)程,阻塞了
在分析卡死原因的時(shí)候,第一步就可以先來(lái)看看當前程序里的各種關(guān)鍵線(xiàn)程所處的狀態(tài)


5、線(xiàn)程狀態(tài)轉換簡(jiǎn)圖

本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久