現(xiàn)代的計(jì)算機(jī),大多遵守 馮諾依曼體系結(jié)構(gòu) (Von Neumann Architecture)
AMD Ryzen 7 580OU with Radeon Graphics
GHz
叫做 CPU 的主頻
這個(gè)數(shù)字越大,CPU 就算的越快,表示 1s 執(zhí)行 32 億條指令
存儲(chǔ)器: 分為外存和內(nèi)存, 用于存儲(chǔ)數(shù)據(jù)(使用二進(jìn)制方式存儲(chǔ))
輸入設(shè)備: 用戶給計(jì)算機(jī)發(fā)號(hào)施令的設(shè)備.
輸出設(shè)備:計(jì)算機(jī)個(gè)用戶匯報(bào)結(jié)果的設(shè)備
針對(duì)存儲(chǔ)空間
- 硬盤 > 內(nèi)存 >> CPU
針對(duì)數(shù)據(jù)訪問速度
- CPU >> 內(nèi)存 > 硬盤
認(rèn)識(shí)計(jì)算機(jī)的祖師爺 – 馮諾依曼
馮·諾依曼(John von Neumann,1903年12月28日-1957年2月8日), 美籍匈牙利數(shù)學(xué)家、計(jì)算機(jī)科學(xué)家、物理學(xué)家,是20世紀(jì)最重要的數(shù)學(xué)家之一。馮·諾依曼是布達(dá)佩斯大學(xué)數(shù)學(xué)博士,在現(xiàn)代計(jì)算機(jī)、博弈論、核武器和生化武器等領(lǐng)域內(nèi)的科學(xué)全才之一,被后人稱為 “現(xiàn)代計(jì)算機(jī)之父”, “博弈論之父”.
電子開關(guān) —— 機(jī)械繼電器 (Mechanical Relay):
電磁繼電器:通過通電 / 不通電來切換開關(guān)狀態(tài),得到 1 或者 0
這樣的數(shù)據(jù)
基于上述的 “電子開關(guān)” 就能構(gòu)造出基本的門電路,可以實(shí)現(xiàn) 1 位(bit) 的基本邏輯運(yùn)算
最基礎(chǔ)的門電路,有三種:
非門:可以對(duì)一個(gè) 0/1 進(jìn)行取反
. 0-> 1
與門:可以針對(duì)兩個(gè) 0/1 進(jìn)行與運(yùn)算
. 1 0 -> 0
或門:可以針對(duì)兩個(gè) 0/1 進(jìn)行或運(yùn)算
. 1 0 -> 1
針對(duì)二進(jìn)制數(shù)據(jù)來進(jìn)行的.不是"邏輯與”,此處是按位與
借助上述的基礎(chǔ)門電路,能構(gòu)造出一個(gè)更復(fù)雜的門電路:異或門
相同為0,相異為1
。1 0 -> 1
基于上述的門電路,還可以搭建出一些運(yùn)算器
半加器:是針對(duì)兩個(gè)比特位,進(jìn)行加法運(yùn)算
基于上述的半加器和全加器,就可以構(gòu)造出一個(gè)針對(duì)多個(gè) bit 位的數(shù)據(jù)進(jìn)行加法運(yùn)算的加法器了
電子開關(guān)=>基礎(chǔ)的門電路=>異或門電路=>半加器=>全加器=>8位加法器
有了加法器之后,就可以計(jì)算不只是加法,還能計(jì)算減法、乘法、除法都是通過這個(gè)加法器來進(jìn)行
CPU里面除了運(yùn)算器之外,還有控制單元和寄存器(Register)
門電路 (電子開關(guān))
CPU芯片來說,上面就集成了非常非常多的這些電子開關(guān),一個(gè)CPU上面的電子開關(guān)越多,就認(rèn)為是計(jì)算能力就越強(qiáng)
CPU里面除了運(yùn)算器之外,還有控制單元和寄存器
寄存器是CPU內(nèi)部用來存儲(chǔ)數(shù)據(jù)的組件
訪問速度:寄存器是內(nèi)存的3-4個(gè)數(shù)量級(jí)
存儲(chǔ)空間:比內(nèi)存小很多很多,現(xiàn)在的x64的cpu (64位的cpu),大概有幾十個(gè)寄存器,每個(gè)寄存器是8個(gè)字節(jié),幾百個(gè)字節(jié),
成本:CPU上面的這個(gè)寄存器,還是非常貴
持久化:掉電之后數(shù)據(jù)丟失
控制單元 CU(Control Unit):
協(xié)調(diào)CPU來去進(jìn)行工作
控制單元最主要的工作,能夠去執(zhí)行指令
后面進(jìn)行詳細(xì)的論述
指令和編程密切相關(guān)。
編程語言,大概分成三類:
1、機(jī)器語言
通過二進(jìn)制的數(shù)字,來表示的不同的操作
不同的CPU (哪怕是同一個(gè)廠商,但是不同型號(hào)的CPU),所支持的機(jī)器語言(指令)也可能存在差別
2、匯編語言
一個(gè)CPU到底支持哪些指令,生產(chǎn)廠商,會(huì)提供一個(gè)**"芯片手冊(cè)”** 詳細(xì)介紹CPU都支持哪些指令,每個(gè)指令都是干啥的
匯編語言和機(jī)器語言是一對(duì)一的關(guān)系 (完全等價(jià))
不同的CPU支持的機(jī)器指令不一樣,不同的CPU上面跑的匯編也不一樣
學(xué)校的大部分的匯編語言都是針對(duì)一款上古神 U,Intel 8086 CPU
3、高級(jí)語言
(C,Java,JS)
指令是如何執(zhí)行的?
自己構(gòu)造出一個(gè)最簡單的芯片手冊(cè):
假設(shè)CPU上有兩個(gè)寄存器
A 00
B 01
0010 1010
這個(gè)操作的意思,就是把1010
內(nèi)存地址上的數(shù)據(jù)給讀取到A
寄存器中
0001 1111
這個(gè)操作的意思,就是把 1111
內(nèi)存地址上的數(shù)據(jù)讀到寄存器 B
中
0100 1000
這個(gè)操作的意思,就是把 A
寄存器的值,存到 1000
這個(gè)內(nèi)存地址中
1000 0100
這個(gè)操作的意思,就是把 00
寄存器和01
寄存器的數(shù)值進(jìn)行相加,結(jié)果放到 00
寄存器里
CPU的工作流程:(通過CU控制單元來實(shí)現(xiàn)的)
- 從內(nèi)存中讀取指令
- 解析指令
- 執(zhí)行指令
咱們編寫的程序,最終都會(huì)被編譯器給翻譯成 CPU 所能識(shí)別的機(jī)器語言指令,在運(yùn)行程序的時(shí)候,操作系統(tǒng)把這樣的可執(zhí)行程序加載到內(nèi)存中,cpu 就一條一條指令的去進(jìn)行讀取,解析,和執(zhí)行,如果再搭配上條件跳轉(zhuǎn),此時(shí),就能實(shí)現(xiàn)條件語句和循環(huán)語句
操作系統(tǒng)是一組做計(jì)算機(jī)資源管理的軟件的統(tǒng)稱。目前常見的操作系統(tǒng)有:Windows系列、Unix系列、
Linux系列、OSX系列、Android系列、iOS系列、鴻蒙等
操作系統(tǒng)是一個(gè)搞 "管理的軟件"
- 對(duì)下,要管理好各種硬件設(shè)備
- 對(duì)上,要給各種軟件提供穩(wěn)定的運(yùn)行環(huán)境
exe
可執(zhí)行文件,都是靜靜的躺在硬盤上的,在你雙擊之前,這些文件不會(huì)對(duì)你的系統(tǒng)有任何影響
但是,一旦你雙擊執(zhí)行這些 exe 文件,操作系統(tǒng)就會(huì)把這個(gè) exe 給加載到內(nèi)存中,并且讓 CPU 開始執(zhí)行exe內(nèi)部的一些指令 (exe里面就存了很多這個(gè)程序?qū)?yīng)的二進(jìn)制指令)
這個(gè)時(shí)候,就已經(jīng)把 exe給執(zhí)行起來,開始進(jìn)行了一些具體的工作
這些運(yùn)行起來的可執(zhí)行文件,稱為 "進(jìn)程"
這些都是機(jī)器上運(yùn)行的進(jìn)程:
jvm
)
- 先描述一個(gè)進(jìn)程 (明確出一個(gè)進(jìn)程上面的一些相關(guān)屬性)
- 再組織若干個(gè)進(jìn)程 (使用一些數(shù)據(jù)結(jié)構(gòu),把很多描述進(jìn)程的信息給放到一起,方便進(jìn)行增刪改查)
描述進(jìn)程:操作系統(tǒng)里面主要都是通過 C/C++
來實(shí)現(xiàn)的,此處的描述其實(shí)就是用的C語言中的 “結(jié)構(gòu)體” (也就和Java的類差不多)
**操作系統(tǒng)中描述進(jìn)程的這個(gè)結(jié)構(gòu)體, "PCB" (process control block)
,進(jìn)程控制塊,這個(gè)東西不是硬件中的那個(gè)PCB板
組織進(jìn)程:典型的實(shí)現(xiàn),就是使用雙向鏈表來把每個(gè)進(jìn)程的PCB
給串起來
操作系統(tǒng)的種類是很多的,內(nèi)部的實(shí)現(xiàn)也是各有不同,咱們此處所討論的情況,是以Linux
這個(gè)系統(tǒng)為例,由于windows, mac 這樣的系統(tǒng),不是開源的,里面的情況我們并不知道
1、pid
(進(jìn)程id)
進(jìn)程的身份標(biāo)識(shí),進(jìn)程的身份證號(hào)
exe
,此時(shí)操作系統(tǒng)就會(huì)把這個(gè) exe 加載到內(nèi)存中,變成進(jìn)程3、文件描述符表:
程序運(yùn)行過程中,經(jīng)常要和文件打交道 (文件是在硬盤上的)
文件操作:打開文件,讀/寫文件,關(guān)閉文件
進(jìn)程每次打開一個(gè)文件,就會(huì)在文件描述符表上多增加一項(xiàng),(個(gè)文件描述符表就可以視為是一個(gè)數(shù)組,里面的每個(gè)元素,又是一個(gè)結(jié)構(gòu)體,就對(duì)應(yīng)一個(gè)文件的相關(guān)信息)
一個(gè)進(jìn)程只要一啟動(dòng),不管你代碼中是否寫了打開 / 操作文件的代碼,都會(huì)默認(rèn)的打開三個(gè)文件 (系統(tǒng)自動(dòng)打開的),標(biāo)準(zhǔn)輸入(System.in),準(zhǔn)輸出(System.out) 標(biāo)準(zhǔn)錯(cuò)誤(System.err)
要想能讓一個(gè)進(jìn)程正常工作,就需要給這個(gè)進(jìn)程分配一些系統(tǒng)資源:內(nèi)存,硬盤,CPU
這個(gè)文件描述符表的下標(biāo),就稱為文件描述符
4、進(jìn)程調(diào)度:
5、并行和并發(fā):
并行和并發(fā)這兩件事, 只是在微觀上有區(qū)分
宏觀上咱們區(qū)分不了,微觀上這里的區(qū)分都是操作系統(tǒng)自行調(diào)度的結(jié)果
例如6個(gè)核心,同時(shí)跑20個(gè)任務(wù)
這20個(gè)任務(wù), 有些是并行的關(guān)系, 有些是并發(fā)的關(guān)系??赡苋蝿?wù)A和任務(wù)B,一會(huì)是并行, 一會(huì)是并發(fā)….都是微觀上操作系統(tǒng)在控制的,在宏觀上感知不到
正因?yàn)樵诤暧^上區(qū)分不了并行并發(fā), 我們?cè)趯懘a的時(shí)候也就不去具體區(qū)分這兩個(gè)詞實(shí)際上通常使用 “并發(fā)” 這個(gè)詞, 來代指并行+并發(fā)
咱們只是在研究操作系統(tǒng)進(jìn)程調(diào)度這個(gè)話題上的時(shí)候, 稍作區(qū)分但是其他場(chǎng)景上基本都是使用并發(fā)作為一個(gè)統(tǒng)稱來代替的,并發(fā)編程
6、調(diào)度
所謂的調(diào)度就是 “時(shí)間管理”,
并發(fā)就是規(guī)劃時(shí)間表的過程,也就是“調(diào)度"的過程
7、狀態(tài)
狀態(tài)就描述了當(dāng)前這個(gè)進(jìn)程接下來應(yīng)該怎么調(diào)度
Linux中的進(jìn)程狀態(tài)還有很多其他的…
8、優(yōu)先級(jí)
先給誰分配時(shí)間,后給誰分配時(shí)間,以及給誰分的多,給誰分的少…
9、記賬信息
統(tǒng)計(jì)了每個(gè)進(jìn)程,都分別被執(zhí)行了多久,分別都執(zhí)行了哪些指令,分別都排隊(duì)等了多久了…
給進(jìn)程調(diào)度提供指導(dǎo)依據(jù)的
10、上下文
就表示了上次進(jìn)程被調(diào)度出 CPU 的時(shí)候,當(dāng)時(shí)程序的執(zhí)行狀態(tài)。下次進(jìn)程上CPU的時(shí)候,就可以恢復(fù)之前的狀態(tài),然后繼續(xù)往下執(zhí)行
進(jìn)程被調(diào)度出CPU之前,要先把CPU中的所有的寄存器中的數(shù)據(jù)都給保存到內(nèi)存中 (PCB的上下文字段中) ,相當(dāng)于存檔了
下次進(jìn)程再被調(diào)度上CPU的時(shí)候,就可以從剛才的內(nèi)存中恢復(fù)這些數(shù)據(jù)到寄存器中,相當(dāng)于讀檔了
存檔+讀檔,存檔存儲(chǔ)的游戲信息,就稱為 “上下文”
進(jìn)程的調(diào)度,其實(shí)就是操作系統(tǒng)在考慮CPU資源如何給各個(gè)進(jìn)程分配
那內(nèi)存資源又是如何分配的呢?
11、虛擬地址空間:
由于操作系統(tǒng)上,同時(shí)運(yùn)行著很多個(gè)進(jìn)程,如果某個(gè)進(jìn)程,出現(xiàn)了bug 進(jìn)程崩潰了,是否會(huì)影響到其他進(jìn)程呢?
現(xiàn)代的操作系統(tǒng) (windows, linux, mac… ) ,能夠做到這一點(diǎn),就是 “進(jìn)程的獨(dú)立性” 來保證的,就依仗了"虛擬地址空間"
例:如果某個(gè)居民核酸變成陽性了,是否會(huì)影響到其他的居民呢?
一旦發(fā)現(xiàn)有人陽性了,就需要立刻封樓封小區(qū),否則就會(huì)導(dǎo)致其他人也被傳染,
這個(gè)情況就類似于早期的操作系統(tǒng),早期的操作系統(tǒng),里面的進(jìn)程都是訪問同一個(gè)內(nèi)存的地址空間。如果某個(gè)進(jìn)程出現(xiàn) bug,把某個(gè)內(nèi)存的數(shù)據(jù)給寫錯(cuò)了,就可能引起其他進(jìn)程的崩潰
解決方案,就是把這個(gè)院子,給劃分出很多的道路
這些道路之間彼此隔離開,每個(gè)人走各自的道理,這個(gè)時(shí)候就沒事了,此時(shí)即使有人確診,也影響不到別人了,
如果把進(jìn)程按照虛擬地址空間的方式給劃分出了很多份,這個(gè)時(shí)候不是每一份就只剩一點(diǎn)了嘛?? 雖然你的系統(tǒng)有百八十個(gè)進(jìn)程,但是實(shí)際上從微觀上看,同時(shí)執(zhí)行的進(jìn)程,就6個(gè)!!
每個(gè)進(jìn)程能夠撈著的內(nèi)存還是挺多的,而且另一方面,也不是所有的進(jìn)程都用那么多的內(nèi)存,有的進(jìn)程 (一個(gè)3A游戲,吃幾個(gè)G),大多數(shù)的進(jìn)程也就只占幾M即可
12、進(jìn)程間通信
進(jìn)程之間現(xiàn)在通過虛擬地址空間,已經(jīng)各自隔離開了,但是在實(shí)際工作中,進(jìn)程之間有的時(shí)候還是需要相互交互的。
例:某業(yè)主A問:兄弟們,誰家有土豆,借我兩個(gè)
業(yè)主B回答:我有土豆,我給你
設(shè)定一個(gè)公共空間,這個(gè)空間是任何居民都可以來訪問的,
讓B先把土豆放到公共空間中,進(jìn)行消毒,再讓A來把這個(gè)公共空間的土豆給取走,彼此就不容易發(fā)生傳染
類似的,咱們的兩個(gè)進(jìn)程之間,也是隔離開的,也是不能直接交互的,操作系統(tǒng)也是提供了類似的 "公共空間”,
進(jìn)程 A 就可以把數(shù)據(jù)見放到公共空間上,進(jìn)程B再取走
進(jìn)程間通信:
操作系統(tǒng)中,提供的 “公共空間” 有很多種,并且各有特點(diǎn),有的存儲(chǔ)空間大,有的小,有的速度快,有的慢.….
操作系統(tǒng)中提供了多種這樣的進(jìn)程間通信機(jī)制,(有些機(jī)制是屬于歷史遺留的,已經(jīng)不適合于現(xiàn)代的程序開發(fā))
現(xiàn)在最主要使用的進(jìn)程間通信方式兩種:
1.文件操作
2.網(wǎng)絡(luò)操作 (socket)
總結(jié):
為啥要有進(jìn)程?因?yàn)槲覀兊南到y(tǒng)支持多任務(wù)了,程序猿也就需要 “并發(fā)編程”
通過多進(jìn)程,是完全可以實(shí)現(xiàn)并發(fā)編程的,但是有點(diǎn)小問題:
如果需要頻繁的創(chuàng)建而 / 銷毀進(jìn)程,這個(gè)事情成本是比較高的,如果需要頻繁的調(diào)度進(jìn)程,這個(gè)事情成本也是比較高的:
對(duì)于資源的申請(qǐng)和放,本身就是一個(gè)比較低效的操作,
創(chuàng)建進(jìn)程就得分配資源:
1)內(nèi)存
2)文件
銷毀進(jìn)程也得釋放資源
1)內(nèi)存
2)文件
如何解決這個(gè)問題?思路有兩個(gè):
Linux
上也把線程稱為輕量級(jí)進(jìn)程(LWP light weight process)
可以把進(jìn)程比作一個(gè)工廠,假設(shè)這個(gè)工廠有一些生產(chǎn)任務(wù),例如要生產(chǎn) 1w 部手機(jī)
要想提高生產(chǎn)效率:
1). 搞兩個(gè)工廠,一個(gè)生產(chǎn) 5k (多創(chuàng)建了一個(gè)進(jìn)程)
2). 還是一個(gè)工廠,在一個(gè)工廠里多加一個(gè)生產(chǎn)線,兩個(gè)生產(chǎn)線并行生產(chǎn),一個(gè)生產(chǎn)線生產(chǎn)5k,(多創(chuàng)建了一個(gè)線程)
最終生產(chǎn)1w個(gè)手機(jī),花的時(shí)間差不多,但是這里的成本就不一樣了
多加一些線程,是不是效率就會(huì)進(jìn)一步提高呢?一般來說是會(huì),但是也不一定
如果線程多了,這些線程可能要競(jìng)爭同一個(gè)資源,這個(gè)時(shí)候,整體的速度就收到了限制,整體硬件資源是有限的
總結(jié)進(jìn)程與線程的區(qū)別:
并發(fā)編程
這樣的場(chǎng)景CPU
上調(diào)度執(zhí)行,線程是操作系統(tǒng)調(diào)度執(zhí)行的基本單位,(前面講的時(shí)間管理,當(dāng)時(shí)咱們是調(diào)度的進(jìn)程,但是更準(zhǔn)確的說,其實(shí)是調(diào)度的線程)
Java這個(gè)生態(tài)中更常使用的并發(fā)編程方式,是多線程
其他的語言,主打的并發(fā)變成又不一樣:
go,主要是通過多協(xié)程的方式實(shí)現(xiàn)并發(fā).
erlang,這個(gè)是通過 actor 模型實(shí)現(xiàn)并發(fā).
JS,是通過定時(shí)器+事件回調(diào)的方式實(shí)現(xiàn)并發(fā).……
多線程仍然是最主流最常見的一種并發(fā)編程的方式
Java 的線程 和 操作系統(tǒng)線程 的關(guān)系:
Thread
類,來表示 / 操作線程,Thread 類可以視為 Java 標(biāo)準(zhǔn)庫提供的 API, 對(duì)操作系統(tǒng)提供的 API 進(jìn)行了進(jìn)一步的抽象和封裝Thread
實(shí)例,其實(shí)和操作系統(tǒng)中的線程是一 一對(duì)應(yīng)的關(guān)系,操作系統(tǒng)提供了一組關(guān)于線程的API
(C語言風(fēng)格),Java對(duì)于這組API
進(jìn)一步封裝了,就成了Thread
類Thread
類的基本用法
通過 Thread
類創(chuàng)建線程,寫法有很多種
其中最簡單的做法,創(chuàng)建子類,繼承自Thread
,并且重寫 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è)線程內(nèi)部要執(zhí)行哪些代碼,每個(gè)線程都是并發(fā)執(zhí)行的 (各自執(zhí)行各自的代碼),因此就需要告知這個(gè)線程,你執(zhí)行的代碼是什么,
run
方法中的邏輯,是在新創(chuàng)建出來的線程中,被執(zhí)行的代碼
并不是我一定義這個(gè)類,一寫run方法,線程就創(chuàng)建出來,相當(dāng)于我把活安排出來了,但是同學(xué)們還沒開始干呢
需要調(diào)用這里的 start
方法,才是真正的在系統(tǒng)中創(chuàng)建了線程,才是真正開始執(zhí)行上面的 run
操作,在調(diào)用 start
之前,系統(tǒng)中是沒有創(chuàng)建出線程的
如果在一個(gè)循環(huán)中不加任何限制,這個(gè)循環(huán)轉(zhuǎn)的速度非常非常快,導(dǎo)致打印的東西太多了,根本看不過來了,就可以加上一個(gè) sleep 操作,來強(qiáng)制讓這個(gè)線程休眠一段時(shí)間
這個(gè)休眠操作,就是強(qiáng)制地讓線程進(jìn)入阻塞狀態(tài),單位是 ms,就是1s 之內(nèi)這個(gè)線程不會(huì)到 cpu 上執(zhí)行
public void run() {
while (true) {
System.out.println("hello thread!");
Thread.sleep(1000);
}
}
這是多線程編程中最常見的一個(gè)異常,線程被強(qiáng)制的中斷了,用 try catch 處理
在一個(gè)進(jìn)程中,至少會(huì)有一個(gè)線程,
在一個(gè) java進(jìn)程中,也是至少會(huì)有一個(gè)調(diào)用 main 方法的線程 (這個(gè)線程不是你手動(dòng)搞出來的)
自己創(chuàng)建的 t 線程 和 自動(dòng)創(chuàng)建的 main 線程,就是并發(fā)執(zhí)行的關(guān)系 (宏觀上看起來是同時(shí)執(zhí)行)
此處的并發(fā) = 并行 + 并發(fā)
宏觀上是區(qū)分不了并行和并發(fā)的,都取決于系統(tǒng)內(nèi)部的調(diào)度
package thread;
// Thread是在java.lang 里的,java.lang 里的類都不需要手動(dòng)導(dǎo)入,類似的還有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();
}
}
}
}
運(yùn)行打?。?/strong>
/* hello main
hello thread!
hello thread!
hello main
hello main
hello thread!
hello thread!
hello main */
現(xiàn)在兩個(gè)線程,都是打印一條,就休眠 1s
當(dāng)1s 時(shí)間到了之后,系統(tǒng)先喚醒誰呢?
看起來這個(gè)順序不是完全確定 (隨機(jī)的)
每一輪,1s 時(shí)間到了之后,到底是先喚醒 main
還是 thread
,這是不確定的 (隨機(jī)的)
操作系統(tǒng)來說,內(nèi)部對(duì)于線程之間的調(diào)度順序,在宏觀上可以認(rèn)為是隨機(jī)的 (搶占式執(zhí)行)
這個(gè)隨機(jī)性,會(huì)給多線程編程帶來很多其他的麻煩
寫法一: 創(chuàng)建子類,繼承自 Thread
寫法二: 創(chuàng)建一個(gè)類,實(shí)現(xiàn) Runnable
接口,再創(chuàng)建 Runnable
實(shí)例傳給Thread
實(shí)例
通過 Runnable 來描述任務(wù)的內(nèi)容
進(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());
}
}
寫法三 / 寫法四: 就是上面兩個(gè)寫法的翻版,使用了匿名內(nèi)部類
創(chuàng)建了一個(gè)匿名內(nèi)部類,繼承自 Thread
類,同時(shí)重寫run方法,同時(shí)再new出這個(gè)匿名內(nè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
,針對(duì)這個(gè)創(chuàng)建的匿名內(nèi)部類,同時(shí)new
出的 Runnable` 實(shí)例傳給 Thread 的構(gòu)造方法
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();
}
}
通常認(rèn)為Runnable
這種寫法更好一點(diǎn),能夠做到讓線程和線程執(zhí)行的任務(wù),更好的進(jìn)行解耦
寫代碼一般希望,高內(nèi)聚,低耦合
Runnable
單純的只是描述了一個(gè)任務(wù),至于這個(gè)任務(wù)是要通過一個(gè)進(jìn)程來執(zhí)行,還是線程來執(zhí)行,還是線程池來執(zhí)行,還是協(xié)程來執(zhí)行,Runnable 本身并不關(guān)心,Runnable 里面的代碼也不關(guān)心
第五種寫法: 相當(dāng)于是第四種寫法的延伸,使用 lambda
表達(dá)式,是使用lambda
代替了 Runnable
而已
package thread;
public class demo6 {
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello thread!");
});
t.start();
}
}
多線程能夠提高任務(wù)完成的效率
測(cè)試:有兩個(gè)整數(shù)變量,分別要對(duì)這倆變量自增10億次,分別使用一個(gè)線程,和兩個(gè)線程
此處不能直接這么記錄結(jié)束時(shí)間,別忘了,現(xiàn) 在這個(gè)求時(shí)間戳的代碼是在 main
線程中
main
和t1
,t2
之間是并發(fā)執(zhí)行的關(guān)系,此處t1
和t2
還沒執(zhí)行完呢,這里就開始記錄結(jié)束時(shí)間了,這顯然是不準(zhǔn)確的
正確做法應(yīng)該是讓main
線程等待 t1
和 t2
跑完了,再來記錄結(jié)束時(shí)間
join
效果就是等待線程結(jié)束,t1.join
就是讓main
線程等待t1
結(jié)束,t2.join
讓 main
線程等待 t2
結(jié)束
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 線程等待 t1 結(jié)束
t2.join(); // 讓 main 線程等待 t2 結(jié)束
long end = System.currentTimeMillis();
System.out.println("消耗時(shí)間: " + (end- begin) + "ms");
}
public static void main(String[] args) throws InterruptedException {
// serial();
concurrency();
}
}
串行執(zhí)行的時(shí)候,時(shí)間大概是600多ms (平均650左右)
兩個(gè)線程并發(fā)執(zhí)行,時(shí)間大概是400多ms (平均450左右)
提升了接近50%
并不是說,一個(gè)線程600多ms,兩個(gè)線程就是300多ms
這倆線程在底層到底是并行執(zhí)行,還是并發(fā)執(zhí)行,不確定,真正并行執(zhí)行的時(shí)候,效率才會(huì)有顯著提升
多線程特別適合于那種CPU密集型的程序,程序要進(jìn)行大量的計(jì)算,使用多線程就可以更充分的利用CPU的多核資源
方法 | 說明 |
---|---|
Thread() | 創(chuàng)建線程對(duì)象 |
Thread(Runnable target) | 使用 Runnable 對(duì)象創(chuàng)建線程對(duì)象 |
Thread(String name) | 創(chuàng)建線程對(duì)象,并命名 |
Thread(Runnable target, String name) | 使用 Runnable 對(duì)象創(chuàng)建線程對(duì)象,并命名 |
【了解】Thread(ThreadGroup group, Runnable target) | 線程可以被用來分組管理,分好的組即為線程組,這 個(gè)目前我們了解即可 |
Thread(String name)
:這個(gè)東西是給線程 (thread對(duì)象) 起一個(gè)名字,起一個(gè)啥樣的名字,不影響線程本身的執(zhí)行
僅僅只是影響到程序猿調(diào)試,可以借助一些工具看到每個(gè)線程以及名字,很容易在調(diào)試中對(duì)線程做出區(qū)分
可以使用 jconsole
來觀察線程的名字,jconsole
是jdk
自帶的一個(gè)調(diào)試工具
jconsole
這里能夠羅列出你系統(tǒng)上的java
進(jìn)程(其他進(jìn)程不行)
屬性 | 獲取方法 |
---|---|
ID | getId() |
名稱 | getName() |
狀態(tài) | getState() |
優(yōu)先級(jí) | getPriority() |
是否后臺(tái)線程 | isDaemon() |
是否存活 | isAlive() |
是否被中斷 | isInterrupted( |
是否后臺(tái)線程:isDaemon()
如果線程是后臺(tái)線程,不影響進(jìn)程退出
如果線程不是后臺(tái)線程 (前臺(tái)線程),就會(huì)影響到進(jìn)程退出
創(chuàng)建的 t1
和 t2
默認(rèn)都是前臺(tái)的線程
即使 main 方法執(zhí)行完畢,進(jìn)程也不能退出,得等 t1
和 t2
都執(zhí)行完,整個(gè)進(jìn)程才能退出!
如果 t1
和 t2
是后臺(tái)線程,此時(shí)如果 main
執(zhí)行完畢,整個(gè)進(jìn)程就直接退出,t1
和 t2
就被強(qiáng)行終止了
是否存活: isAlive()
操作系統(tǒng)中對(duì)應(yīng)的線程是否正在運(yùn)行
Thread t
對(duì)象的生命周期和內(nèi)核中對(duì)應(yīng)的線程,生命周期并不完全一致
創(chuàng)建出t
對(duì)象之后,在調(diào)用 start 之前,系統(tǒng)中是沒有對(duì)應(yīng)線程的
在run
方法執(zhí)行完了之后,系統(tǒng)中的線程就銷毀了,但是t
這個(gè)對(duì)象可能還存在,通過 isAlive
就能判定當(dāng)前系統(tǒng)的線程的運(yùn)行情況
如果調(diào)用 start
之后,run
執(zhí)行完之前,isAlive
就是返回true
。如果調(diào)用start
之前,run
執(zhí)行完之后,isAlive
就返回 false
ID
是線程的唯一標(biāo)識(shí),不同線程不會(huì)重復(fù)
名稱是各種調(diào)試工具用到
優(yōu)先級(jí)高的線程理論上來說更容易被調(diào)度到
關(guān)于后臺(tái)線程,需要記住一點(diǎn):JVM會(huì)在一個(gè)進(jìn)程的所有非后臺(tái)線程結(jié)束后,才會(huì)結(jié)束運(yùn)行。
是否存活,即簡單的理解,為 run 方法是否運(yùn)行結(jié)束了
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() + ": 我還活著");
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()
+ ": 名稱: " + thread.getName());
System.out.println(Thread.currentThread().getName()
+ ": 狀態(tài): " + thread.getState());
System.out.println(Thread.currentThread().getName()
+ ": 優(yōu)先級(jí): " + thread.getPriority());
System.out.println(Thread.currentThread().getName()
+ ": 后臺(tái)線程: " + thread.isDaemon());
System.out.println(Thread.currentThread().getName()
+ ": 活著: " + 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());
}
}
start()
決定了系統(tǒng)中是不是真的創(chuàng)建出線程
start 和 run 的區(qū)別:
run()
單純的只是一個(gè)普通的方法,描述了任務(wù)的內(nèi)容start()
則是一個(gè)特殊的方法,內(nèi)部會(huì)在系統(tǒng)中創(chuàng)建線程
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ā)執(zhí)行,而 run()
循環(huán)打印 hello thread
run
方法只是一個(gè)普通的方法,你在main
線程里調(diào)用run
,其實(shí)并沒有創(chuàng)建新的線程,這個(gè)循環(huán)仍然是在 main
線程中執(zhí)行的
既然是在一個(gè)線程中執(zhí)行,代碼就得從前到后的按順序運(yùn)行,運(yùn)行第一個(gè)循環(huán),再運(yù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
中斷線程:讓一個(gè)線程停下來
線程停下來的關(guān)鍵,是要讓線程對(duì)應(yīng)的 run
方法執(zhí)行完
還有一個(gè)特殊的是 main
這個(gè)線程,對(duì)于main
來說,得是main
方法執(zhí)行完,線程就完了)
如何中斷線程:
1、標(biāo)志位
可以手動(dòng)的設(shè)置一個(gè)標(biāo)志位 (自己創(chuàng)建的變量,boolean),來控制線程是否要執(zhí)行結(jié)束
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 設(shè)為true,這個(gè)循環(huán)就退出了,進(jìn)一步的 run 就執(zhí)行完了,再進(jìn)一步就是線程執(zhí)行結(jié)束了
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
isQuit = true;
System.out.println("終止 t 線程!");
}
}
運(yùn)行輸出:
hello thread
hello thread
hello thread
hello thread
hello thread
終止 t 線程!
在其他線程中控制這個(gè)標(biāo)志位,就能影響到這個(gè)線程的結(jié)束
此處因?yàn)椋?strong>多個(gè)線程共用同一個(gè)虛擬地址空間,因此,main
線程修改的 isQuit
和 t
線程判定的 isQuit
,是同一個(gè)值
2、interrupted()
但是,isQuit 并不嚴(yán)謹(jǐn),更好的做法,使用 Thread 中內(nèi)置的一個(gè)標(biāo)志位來進(jìn)行判定
Thread.interrupted()
這是一個(gè)靜態(tài)的方法
Thread.currentThread().isInterrupted()
這是實(shí)例方法,其中 currentThread
能夠獲取到當(dāng)前線程的實(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();
}
// 在主線程中,調(diào)用 interrupt 方法,中斷這個(gè)線程
// 讓 t 線程被中斷
t.interrupt();
}
}
運(yùn)行此代碼,打印五次 hello thread 后,出現(xiàn)異常,然后繼續(xù)打印 hello thread
調(diào)用 t.interrupt()
這個(gè)方法,可能產(chǎn)生兩種情況:
1). 如果 t
線程是處在就緒狀態(tài),就是設(shè)置線程的標(biāo)志位為 true
2). 如果 t
線程處在阻塞狀態(tài) (sleep 休眠了),就會(huì)觸發(fā)一個(gè) InterruptException
這個(gè)代碼絕大部分情況,都是在休眠狀態(tài)阻塞
此處的中斷,是希望能夠立即產(chǎn)生效果的
如果線程已經(jīng)是阻塞狀態(tài)下,此時(shí)設(shè)置標(biāo)志位就不能起到及時(shí)喚醒的效果
調(diào)用這個(gè) interrupt
方法,就會(huì)讓 sleep
觸發(fā)一個(gè)異常,從而導(dǎo)致線程從阻塞狀態(tài)被喚醒
當(dāng)下的代碼,一旦觸發(fā)了異常之后,就進(jìn)入了catch
語句,在catch
中,就單純的只是打了一個(gè)日志
printStackTrace
是打印當(dāng)前出現(xiàn)異常位置的代碼調(diào)用棧,打完日志之后,就直接繼續(xù)運(yùn)行
解決方法:
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();
// 當(dāng)前觸發(fā)異常后,立即退出循環(huán)
System.out.println("這個(gè)是收尾工作");
break;
}
}
});
t.start();
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 在主線程中,調(diào)用 interrupt 方法,中斷這個(gè)線程
// 讓 t 線程被中斷
t.interrupt();
}
}
運(yùn)行結(jié)果:
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è)代碼中的線程有很多個(gè),隨時(shí)哪個(gè)線程都可能會(huì)終止
Thread.interrupted()
這個(gè)方法判定的標(biāo)志位是Thread
的static
成員,一個(gè)程序中只有一個(gè)標(biāo)志位
Thread.currentThread().isInterrupted()
這個(gè)方法判定的標(biāo)志位是 Thread
的普通成員,每個(gè)示例都有自己的標(biāo)志位,一般就無腦使用這個(gè)方法即可
方法 | 說明 |
---|---|
public void interrupt() | 中斷對(duì)象關(guān)聯(lián)的線程,如果線程正在阻塞,則以異常方式通知, 否則設(shè)置標(biāo)志位 |
public static boolean interrupted() | 判斷當(dāng)前線程的中斷標(biāo)志位是否設(shè)置,調(diào)用后清除標(biāo)志位 |
public boolean isInterrupted() | 判斷對(duì)象關(guān)聯(lián)的線程的標(biāo)志位是否設(shè)置,調(diào)用后不清除標(biāo)志位 |
多個(gè)線程之間,調(diào)度順序是不確定的,線程之間的執(zhí)行是按照調(diào)度器來安排的,這個(gè)過程可以視為是 “無序,隨機(jī)”,這樣不太好,有些時(shí)候,我們需要能夠控制線程之間的順序
線程等待,就是其中一種控制線程執(zhí)行順序的手段
此處的線程等待,主要是控制線程結(jié)束的先后順序
join()
:調(diào)用 join
的時(shí)候,哪個(gè)線程調(diào)用的 join
,哪個(gè)線程就會(huì)阻塞等待,要等到對(duì)應(yīng)的線程執(zhí)行完畢為止 (對(duì)應(yīng)線程的 run 執(zhí)行完)
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();
// 在主線程中,使用等待操作,等 t 線程執(zhí)行結(jié)束
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
首先,調(diào)用這個(gè)方法的線程是 main
線程,針對(duì)t
這個(gè)線程對(duì)象調(diào)用的,此時(shí)就是讓 main
等待t
調(diào)用 join
之后,main
線程就會(huì)進(jìn)入阻塞狀態(tài) (暫時(shí)無法在cpu上執(zhí)行)
代碼執(zhí)行到 join` 這一行,就暫時(shí)停下了,不繼續(xù)往下執(zhí)行了
那么join
什么時(shí)候能繼續(xù)往下走,恢復(fù)成就緒狀態(tài)呢?
就是等到 t
線程執(zhí)行完畢 ( t
的 run
方法跑完了)
通過線程等待,就是在**控制讓** t
先結(jié)束,main
后結(jié)束,一定程度上的干預(yù)了這兩個(gè)線程的執(zhí)行順序
這是代碼中控制的先后順序,就像剛才寫的自增 100 億次這個(gè)代碼,計(jì)時(shí)操作就是要在計(jì)算線程執(zhí)行完之后再執(zhí)行
但是 join
操作默認(rèn)情況下,是死等,不見不散,這不合理
join
提供了另外一個(gè)版本,就是可以設(shè)置等待時(shí)間,最長等待多久,如果等不到,就不等了
try {
t.join(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
進(jìn)入 join
也會(huì)產(chǎn)生阻塞,這個(gè)阻塞不會(huì)一直持續(xù)下去,如果 10s 之內(nèi),t
線程結(jié)束了,此時(shí) join
直接返回
如果10s之后,t
仍然不結(jié)束, 此時(shí)join
也就直接返回
日常開發(fā)中涉及到的一些 "等待” 相關(guān)的操作,一般都不會(huì)是死等,而是會(huì)有這樣的 "超時(shí)時(shí)間"
Thread.currentThread()
就能夠獲取到當(dāng)前線程的引用 (Thread
實(shí)例的引用),哪個(gè)線程調(diào)用的這個(gè)currentThread
,就獲取到的是哪個(gè)線程的實(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 線程中調(diào)用的,拿到的就是 main 這個(gè)線程的實(shí)例
System.out.println(Thread.currentThread().getName()); // main
}
}
this.getName()
:對(duì)于這個(gè)代碼來,是通過繼承 Thread 的方式來創(chuàng)建線程
此時(shí)在 run 方法中,直接通過 this,拿到的就是當(dāng)前 Thread 的實(shí)例
Thread t = new Thread(){
@Override
public void run() {
System.out.println(this.getName());
}
};
t.start();
此處的 this
不是指向 Thread
類型了,而是指向 Runnable
,而 Runnable
只是一個(gè)單純的任務(wù),沒有 name
屬性的
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(this.getName()); // err
}
});
t.start();
要想拿到線程的名字,只能通過 Thread.currentThread()
lambda
表達(dá)式效果同 Runnable
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
t.start();
sleep
所謂的休眠到底是在干啥?
進(jìn)程:PCB+雙向鏈表,這個(gè)說法是針對(duì)只有一個(gè)線程的進(jìn)程是如此的
如果是一個(gè)進(jìn)程有多個(gè)線程,此時(shí)每個(gè)線程都有一個(gè)PCB,一個(gè)進(jìn)程對(duì)應(yīng)的就是一組PCB了
PCB
上有一個(gè)字段tgroupld
,這個(gè)id
其實(shí)就相當(dāng)于進(jìn)程的id
,同一個(gè)進(jìn)程中的若干個(gè)線程的 tgroupld
是相同的
process control block
進(jìn)程控制塊 和 線程有啥關(guān)系?其實(shí) Linux
內(nèi)核不區(qū)分進(jìn)程和線程
進(jìn)程線程是程序猿寫應(yīng)用程序代碼,搞出來的詞,實(shí)際上 Linux
內(nèi)核只認(rèn)PCB
!
在內(nèi)核里 Linux
把線程稱為輕量級(jí)進(jìn)程
sleep
方法,這個(gè) PCB
就會(huì)進(jìn)入到阻塞隊(duì)列
操作系統(tǒng)調(diào)度線程的時(shí)候,就只是從就緒隊(duì)列中挑選合適的 PCB
到 CPU
上運(yùn)行,阻塞隊(duì)列里的 PCB
就只能干等著,當(dāng)睡眠時(shí)間到了,系統(tǒng)就會(huì)把剛才這個(gè) PCB
從阻塞隊(duì)列挪回到就緒隊(duì)列,以上情況都是在 Linux
系統(tǒng)
內(nèi)核中的很多工作都依賴大量的數(shù)據(jù)結(jié)構(gòu),但凡是需要管理很多數(shù)據(jù)的程序,都大量的依賴數(shù)據(jù)結(jié)構(gòu)
進(jìn)程有狀態(tài):就緒,阻塞
這里的狀態(tài)就決定了系統(tǒng)按照啥樣的態(tài)度來調(diào)度這個(gè)進(jìn)程,這里相當(dāng)于是針對(duì)一個(gè)進(jìn)程中只有一個(gè)線程的情況
更常見的情況下,一個(gè)進(jìn)程中包含了多個(gè)線程,所謂的狀態(tài),其實(shí)是綁定在線程上
Linux 中,PCB 其實(shí)是和線程對(duì)應(yīng)的,一個(gè)進(jìn)程對(duì)應(yīng)著一組 PCB
上面說的 “就緒" 和 “阻塞” 都是針對(duì)系統(tǒng)層面上的線程的狀態(tài) (PCB)
在 Java 中Thread
類中,對(duì)于線程的狀態(tài),又進(jìn)—步的細(xì)化了
1、 NEW
:安排了工作, 還未開始行動(dòng)
把 Thread 對(duì)象創(chuàng)建好了,但是還沒有調(diào)用 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
:工作完成了
操作系統(tǒng)中的線程已經(jīng)執(zhí)行完畢,銷毀了但是 Thread
對(duì)象還在,獲取到的狀態(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 內(nèi)部搞出來的狀態(tài),就和操作系統(tǒng)中的 PCB 里的狀態(tài)就沒啥關(guān)系
3、 RUNNABLE
:可工作的,又可以分成正在工作中和即將開始工作
就緒狀態(tài),處于這個(gè)狀態(tài)的線程,就是在就緒隊(duì)列中,隨時(shí)可以被調(diào)度到 CPU 上
如果代碼中沒有進(jìn)行 sleep
,也沒有進(jìn)行其他的可能導(dǎo)致阻塞的操作,代碼大概率是處在 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
}
}
一直持續(xù)不斷的執(zhí)行這里的循環(huán),隨時(shí)系統(tǒng)想調(diào)度它上cpu都是隨時(shí)可以的
4、TIMED_WAITING
:這幾個(gè)都表示排隊(duì)等著其他事情
代碼中調(diào)用了sleep
,就會(huì)進(jìn)入到 TIMED_WAITIN
,意思就是當(dāng)前的線程在一定時(shí)間之內(nèi),是阻塞的狀態(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è)都表示排隊(duì)等著其他事情
當(dāng)前線程在等待鎖,導(dǎo)致了阻塞(阻塞狀態(tài)之一) --synchronized
6、WAITING
:這幾個(gè)都表示排隊(duì)等著其他事情
當(dāng)前線程在等待喚醒,導(dǎo)致了阻塞(阻塞狀態(tài)之一) --wait
為啥要這么細(xì)分?這是非常有好處的:
開發(fā)過程中經(jīng)常會(huì)遇到一種情況,程序 "卡死” 了
一些關(guān)鍵的線程,阻塞了
在分析卡死原因的時(shí)候,第一步就可以先來看看當(dāng)前程序里的各種關(guān)鍵線程所處的狀態(tài)
聯(lián)系客服