免费视频淫片aa毛片_日韩高清在线亚洲专区vr_日韩大片免费观看视频播放_亚洲欧美国产精品完整版

打開APP
userphoto
未登錄

開通VIP,暢享免費(fèi)電子書等14項(xiàng)超值服

開通VIP
計(jì)算機(jī)是如何工作的,Java多線程編程

一、馮諾依曼體系

現(xiàn)代的計(jì)算機(jī),大多遵守 馮諾依曼體系結(jié)構(gòu) (Von Neumann Architecture)


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

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ī)之父”, “博弈論之父”.


二、CPU 基本工作流程

1、邏輯門

1.1、電磁繼電器

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


1.2、門電路 (Gate Circuit)

基于上述的 “電子開關(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


1.3、運(yùn)算器

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

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


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


1.4、加法器

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


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

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

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


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

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ì)的論述


3、指令(Instruction)

指令和編程密切相關(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)的)

  1. 從內(nèi)存中讀取指令
  2. 解析指令
  3. 執(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)(Operating System)

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

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

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

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

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)程:

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

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

  1. 先描述一個(gè)進(jìn)程 (明確出一個(gè)進(jìn)程上面的一些相關(guān)屬性)
  2. 再組織若干個(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),不是開源的,里面的情況我們并不知道


3、PCB中的一些屬性:

1、pid (進(jìn)程id)
進(jìn)程的身份標(biāo)識(shí),進(jìn)程的身份證號(hào)


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

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)度:

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

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

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

并行和并發(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ā)編程


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

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)度

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

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ǔ)的游戲信息,就稱為 “上下文”


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

進(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即可


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

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é):


四、多線程

1、線程(Thread)

為啥要有進(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è):

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

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

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

可以把進(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ū)別:

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

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 多線程編程

Java 的線程 和 操作系統(tǒng)線程 的關(guān)系:

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

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

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)建出線程的


2、線程之間是并發(fā)執(zhí)行的

如果在一個(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ì)給多線程編程帶來很多其他的麻煩


3、Thread 類創(chuàng)建線程的寫法

寫法一: 創(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 線程中
maint1 ,t2 之間是并發(fā)執(zhí)行的關(guān)系,此處t1t2 還沒執(zhí)行完呢,這里就開始記錄結(jié)束時(shí)間了,這顯然是不準(zhǔn)確的
正確做法應(yīng)該是main線程等待 t1t2 跑完了,再來記錄結(jié)束時(shí)間
join 效果就是等待線程結(jié)束,t1.join就是讓main 線程等待t1 結(jié)束,t2.joinmain 線程等待 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類的其他的屬性和方法

1、Thread 的常見構(gòu)造方法

方法說明
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觀察線程的名字,jconsolejdk自帶的一個(gè)調(diào)試工具


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




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

屬性獲取方法
IDgetId()
名稱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)建的 t1t2 默認(rèn)都是前臺(tái)的線程
即使 main 方法執(zhí)行完畢,進(jìn)程也不能退出,得等 t1t2 都執(zhí)行完,整個(gè)進(jìn)程才能退出!
如果 t1t2 是后臺(tái)線程,此時(shí)如果 main 執(zhí)行完畢,整個(gè)進(jìn)程就直接退出,t1t2 就被強(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());
    }
}

3、Thread中的一些重要方法

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

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


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

中斷線程:讓一個(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 線程修改的 isQuitt 線程判定的 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)志位是Threadstatic成員,一個(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)志位

3.3、線程等待-join()

多個(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í)行完畢 ( trun方法跑完了)
通過線程等待,就是在**控制讓** 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í)間"


3.4、獲取當(dāng)前線程的引用

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();

3.5、休眠當(dāng)前線程

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)程


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

操作系統(tǒng)調(diào)度線程的時(shí)候,就只是從就緒隊(duì)列中挑選合適的 PCBCPU 上運(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)


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

進(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)


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

本站僅提供存儲(chǔ)服務(wù),所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊舉報(bào)。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
Java多線程編程總結(jié)
Java筆記(六 程序、進(jìn)程和線程)
Java 多線程的三種實(shí)現(xiàn)方法
進(jìn)程,線程
線程知識(shí)
Java并發(fā)編程之線程的創(chuàng)建
更多類似文章 >>
生活服務(wù)
分享 收藏 導(dǎo)長圖 關(guān)注 下載文章
綁定賬號(hào)成功
后續(xù)可登錄賬號(hào)暢享VIP特權(quán)!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服