??谧鼍W(wǎng)站公司哪家好網(wǎng)頁(yè)快照
文章目錄
- 線程
- 在 Java 代碼中編寫(xiě)多線程程序
- Thread 標(biāo)準(zhǔn)庫(kù)
- 創(chuàng)建線程的寫(xiě)法
- 1 . 繼承 Thread 類
- 代碼
- 回調(diào)函數(shù)
- 休眠操作:`sleep()`
- 搶占式執(zhí)行
- 觀察線程
- jconsole
- IDEA 內(nèi)置調(diào)試器
- 2 . 實(shí)現(xiàn) Runnable 接口
- 代碼
- 3. 匿名內(nèi)部類創(chuàng)建 Thread ?類對(duì)象
- 代碼
- 匿名內(nèi)部類
- 4.匿名內(nèi)部類創(chuàng)建Runnable?類對(duì)象
- 代碼
- 5.lambda 表達(dá)式創(chuàng)建 Runnable ?類對(duì)象
線程
-
可以理解成更輕量的進(jìn)程,也能解決[[01 計(jì)算機(jī)是如何工作的#^87b85a|并發(fā)編程]]的問(wèn)題,但是創(chuàng)建/銷毀的開(kāi)銷,要比進(jìn)程更低
-
因此多線程編程就成了當(dāng)下主流的并發(fā)編程方式
-
這個(gè)基本上是我們以后工作中天天用到,面試中考查的重點(diǎn)
#高頻面試 -
在系統(tǒng)中,線程同樣是通過(guò) [[01 計(jì)算機(jī)是如何工作的#^7eb7b0|PCB]] 來(lái)描述的(Linux)
- 一個(gè)進(jìn)程,是一組 PCB
- 一個(gè)線程,是一個(gè) PCB
-
一個(gè)進(jìn)程中可以包含多個(gè)線程
-
此時(shí)每個(gè)線程都可以獨(dú)立的去 CPU 上調(diào)度執(zhí)行
- 線程是系統(tǒng)“調(diào)度執(zhí)行”的基本單位
- 進(jìn)程是系統(tǒng)“資源分配”的基本單位
-
一個(gè)可執(zhí)行程序運(yùn)行的時(shí)候(雙擊)
- 操作系統(tǒng)會(huì)創(chuàng)建進(jìn)程,給這個(gè)程序分配各種系統(tǒng)資源(CPU,內(nèi)存,硬盤(pán),網(wǎng)絡(luò)帶寬…)
- 同時(shí)也會(huì)在這個(gè)進(jìn)程中創(chuàng)建一個(gè)或多個(gè)線程,這些線程再去 CPU 上調(diào)度執(zhí)行
-
同一個(gè)進(jìn)程中的線程,共用同一份資源
-
-
線程比進(jìn)程更輕量主要體現(xiàn)在可以資源共用
- 創(chuàng)建線程,省去了“分配資源”的過(guò)程
- 銷毀進(jìn)程,省去了“釋放資源”的過(guò)程
-
一旦創(chuàng)建進(jìn)程,同時(shí)也會(huì)創(chuàng)建第一個(gè)線程==>分配資源,時(shí)間相對(duì)來(lái)說(shuō)較慢
- 一旦后續(xù)創(chuàng)建第二個(gè)、三個(gè)線程,就不必再重新分配資源了,用創(chuàng)建第一個(gè)線程時(shí)分配的資源
-
當(dāng)線程數(shù)目越來(lái)越多之后,此時(shí)效率也沒(méi)辦法進(jìn)一步提升了(桌子的空間是有限的),當(dāng)滑稽老鐵數(shù)目達(dá)到一定程度后,有些人就夠不到桌子了,就吃不到了
-
能夠提升效率,關(guān)鍵是充分利用多核心進(jìn)行“并行執(zhí)行”
-
如果只是“微觀并發(fā)”,速度是沒(méi)有提升的,真正能提升速度的是“并行”
-
如果線程數(shù)目太多,比如超出了 CPU 核心數(shù)目,此時(shí)就無(wú)法在微觀上完成所有線程的“并行執(zhí)行”,勢(shì)必會(huì)存在嚴(yán)重的競(jìng)爭(zhēng)
-
當(dāng)線程多了之后,此時(shí)就容易發(fā)生“沖突”
-
由于多個(gè)線程,使用的是同一份資源(內(nèi)存資源)
-
若多個(gè)線程針對(duì)同一個(gè)變量進(jìn)行讀寫(xiě)操作(尤其是寫(xiě)操作),就容易發(fā)生沖突
-
一旦發(fā)生沖突,就可能使程序出現(xiàn)問(wèn)題==>“線程安全問(wèn)題”
-
一旦某個(gè)線程拋出異常,這個(gè)時(shí)候,如果不能妥善處理,就可能導(dǎo)致整個(gè)進(jìn)程都崩潰,因此其他線程就會(huì)隨之崩潰
-
-
-
進(jìn)程和線程的概念與區(qū)別(高頻面試題,操作系統(tǒng)話題下出場(chǎng)頻率最高的問(wèn)題)
#高頻面試- 進(jìn)程包含線程
- 一個(gè)進(jìn)程里面可以有多個(gè)線程
- 進(jìn)程是系統(tǒng)資源分配的基本單位
線程是系統(tǒng)調(diào)度執(zhí)行的基本單位 - 同一個(gè)進(jìn)程的線程之間,共用一份系統(tǒng)資源(內(nèi)存,硬盤(pán),網(wǎng)絡(luò)帶寬等)
- 尤其是“內(nèi)存資源”,就是代碼中定義的變量/對(duì)象…
- 編程中,多個(gè)線程,是可以共用一份變量的
- 線程是當(dāng)下實(shí)現(xiàn)并發(fā)編程的主流方式,通過(guò)多線程,就可以充分利用好多核 CPU
- 但也不是線程數(shù)越多就一定越好,當(dāng)線程數(shù)達(dá)到一定的程度,把多個(gè)核心都利用充分之后,再增加線程,就無(wú)法再提高效率了,甚至可能會(huì)影響效率(線程調(diào)度也是有開(kāi)銷的)
- 多個(gè)線程之間可能會(huì)相互影響
- 線程安全問(wèn)題:一個(gè)線程拋出異常,也可能會(huì)把其他線程也一起帶走
- 多個(gè)進(jìn)程之間一般不會(huì)相互影響
- 進(jìn)程的隔離型:一個(gè)進(jìn)程崩潰了,不會(huì)影響其他進(jìn)程
- 進(jìn)程包含線程
在 Java 代碼中編寫(xiě)多線程程序
- 線程本身是操作系統(tǒng)提供的概念,操作系統(tǒng)提供 API 供程序猿調(diào)用
- 但不同的系統(tǒng),提供的 API 是不同的(Windows 創(chuàng)建線程的 API 和 Linux 的差別非常大)
- Java(JVM)把這些系統(tǒng) API 封裝好了,咱們不需要關(guān)注系統(tǒng)原生 API,只需要了解好 Java 提供的這一套 API 就好了
Thread 標(biāo)準(zhǔn)庫(kù)
- 這個(gè)類負(fù)責(zé)完成多線程的相關(guān)開(kāi)發(fā)
創(chuàng)建線程的寫(xiě)法
1 . 繼承 Thread 類
代碼
package thread; class MyThread extends Thread { //繼承Thread類的目的是重寫(xiě)里面的run()方法 @Override public void run() { //這里寫(xiě)的代碼就是即將創(chuàng)建的線程所需要執(zhí)行的邏輯 while (true) { System.out.println("hello thread"); //休眠操作,避免CPU消耗過(guò)大 try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }}
} public class Demo1 { public static void main(String[] args) { MyThread t = new MyThread(); //創(chuàng)建線程,與主線程各自獨(dú)立,并發(fā)執(zhí)行 t.start(); //t.run(); 不會(huì)創(chuàng)建新線程,在主線程中執(zhí)行,但執(zhí)行不到//因?yàn)橛捎诓皇蔷€程,所以不會(huì)并發(fā)執(zhí)行,所以一直執(zhí)行創(chuàng)建的MyThread線程,死循環(huán)while(true) { System.out.println("hello main"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } }}
}
這個(gè)代碼運(yùn)行起來(lái)是一個(gè)進(jìn)程,但包含了兩個(gè)線程
主線程——>
main
方法(每個(gè)進(jìn)程必有)
自創(chuàng)建的新線程——>t.start()
隨后主線程和新線程就會(huì)并發(fā)/并行的在 CPU 上執(zhí)行
-
創(chuàng)建一個(gè)類,繼承于標(biāo)準(zhǔn)庫(kù)的
Thread
,并重寫(xiě)run()
方法Thread
類可以直接被繼承,因?yàn)樗鼇?lái)自java. lang
這個(gè)包,而這個(gè)包是默認(rèn)導(dǎo)入的,里面所有的類都可以直接使用- 繼承
Thread
類的主要目的是重寫(xiě)run()
方法 run()
中的方法就是即將創(chuàng)建出的線程要執(zhí)行的邏輯
-
在
main
方法里創(chuàng)建剛才那個(gè)類的實(shí)例,再使用提供的start()
方法來(lái)創(chuàng)建線程- 調(diào)用
run()
就會(huì)在進(jìn)程內(nèi)部創(chuàng)建出一個(gè)新的線程,新的線程就會(huì)執(zhí)行剛才run
里面的代碼 - 線程明細(xì):
- 主線程:調(diào)用
main
函數(shù)的方法需要一個(gè)專門(mén)的線程來(lái)執(zhí)行,稱為主線程 t1.start();
:這是創(chuàng)建的一個(gè)新進(jìn)程,與主線程之間是并發(fā)/并行關(guān)系在 CPU 上執(zhí)行- 這里調(diào)用
start()
是創(chuàng)建了一個(gè)新的線程,隨后執(zhí)行新線程里面的邏輯,而直接調(diào)用run()
方法的話不會(huì)創(chuàng)建新的線程
- 這里調(diào)用
- 主線程:調(diào)用
- 調(diào)用
-
運(yùn)行結(jié)果
回調(diào)函數(shù)
(非常重要的概念)
run()
方法并沒(méi)有被手動(dòng)調(diào)用,但是最終也執(zhí)行了,- 這種被用戶定義了,但沒(méi)手動(dòng)調(diào)用的方法,最終是被系統(tǒng)/庫(kù)/框架調(diào)用了,此時(shí)這樣的方法就叫“回調(diào)函數(shù)(callback)”
- Java 數(shù)據(jù)結(jié)構(gòu)中,優(yōu)先級(jí)隊(duì)列(堆),必須先定義好對(duì)象的“比較規(guī)則”
- Comparable==>compareTo
comparator==>compare
都是自己定義了,但沒(méi)有調(diào)用,此時(shí)都是由標(biāo)準(zhǔn)庫(kù)本身內(nèi)部的邏輯負(fù)責(zé)調(diào)用的
休眠操作:sleep()
- 可以讓循環(huán)每循環(huán)一次就休息一下,避免 CPU 消耗過(guò)大。單位是 ms(毫秒),1000 ms = 1 s
- 這是一個(gè)靜態(tài)方法(類方法)
-
可以直接通過(guò)類名進(jìn)行訪問(wèn)
類名. 方法名
,不需要實(shí)例化對(duì)象,通過(guò)對(duì)象來(lái)訪問(wèn) -
這里會(huì)報(bào)錯(cuò),使用
Alt+Enter
-
IDEA 自動(dòng)生成 try/catch,catch 中默認(rèn)的代碼有兩種風(fēng)格
- 再次拋出一個(gè)異常:
throw new RuntimeException(e);
- 只是打印異常信息:
e.printStackTrace();
- 但實(shí)際開(kāi)發(fā)中不止這倆
- 可能進(jìn)行“重試”
- 可能進(jìn)行“回滾”
- 可能會(huì)通過(guò)短信/郵件/微信/電話向程序猿報(bào)警
- 再次拋出一個(gè)異常:
-
-
搶占式執(zhí)行
- 多個(gè)線程之間,誰(shuí)先去 CPU 上調(diào)度執(zhí)行,這個(gè)過(guò)程是“不確定的”,這個(gè)調(diào)度順序取決于內(nèi)核里面的“調(diào)度器”的實(shí)現(xiàn)
- 調(diào)度器里面有一套規(guī)則,但是我們作為程序開(kāi)發(fā)沒(méi)法進(jìn)行干預(yù),也感受不到,只能把這個(gè)過(guò)程近似于隨機(jī)
觀察線程
jconsole
- 可以借助第三方工具來(lái)看這兩個(gè)進(jìn)程的情況
- JDK 中的 bin 目錄(binary 二進(jìn)制,里面放的都是可執(zhí)行程序)
- 通過(guò)這個(gè)可以看到 Java 中進(jìn)程運(yùn)行情況
- 遠(yuǎn)程進(jìn)程:其他機(jī)器上的進(jìn)程,需要通過(guò)網(wǎng)絡(luò)連接
- 本地進(jìn)程:正在運(yùn)行的進(jìn)程
- 一個(gè) Java 線程中,不僅僅只有一個(gè)線程,其實(shí)有很多
- 代碼中自己創(chuàng)建的線程命名的規(guī)律是
Thread-數(shù)字
- 主要的線程(主線程和自己創(chuàng)建的線程)
- 調(diào)用棧(非常有用)
當(dāng)代碼出現(xiàn)問(wèn)題,拋出異常,進(jìn)程終止時(shí),可以查看對(duì)應(yīng)的調(diào)用棧找到出現(xiàn)問(wèn)題的語(yǔ)句,以及這個(gè)代碼是如何一層一層被調(diào)用過(guò)去的
- 調(diào)用棧(非常有用)
- 其他進(jìn)程:主要起到輔助作用
- 垃圾回收:在合適的時(shí)機(jī),釋放不使用的對(duì)象
- 統(tǒng)計(jì)信息/調(diào)試信息:比如現(xiàn)在通過(guò) jconsole 能查看到一個(gè) Java 進(jìn)程的詳情
- 通過(guò)這個(gè)可以看到 Java 中進(jìn)程運(yùn)行情況
- JDK 中的 bin 目錄(binary 二進(jìn)制,里面放的都是可執(zhí)行程序)
IDEA 內(nèi)置調(diào)試器
- 通過(guò) IDEA 內(nèi)置的調(diào)試器,也能看到類似的信息
2 . 實(shí)現(xiàn) Runnable 接口
代碼
package thread; //通過(guò)Runnable的方式來(lái)創(chuàng)建線程
class MyRunnable implements Runnable { @Override public void run() { //描述線程要完成的邏輯 while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } }
} public class Demo2 { public static void main(String[] args) throws InterruptedException { MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); //通過(guò)Thread創(chuàng)建線程,線程要執(zhí)行的任務(wù)是通過(guò)Runnable來(lái)描述的,不是通過(guò)Thread自己thread.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); }}
}
因?yàn)?
Runnable
是一個(gè)interface
(接口),所以要用implements
(實(shí)現(xiàn))
Runnable
是用來(lái)描述“要執(zhí)行的任務(wù)”是什么
通過(guò)Thread
創(chuàng)建線程,線程要執(zhí)行的任務(wù)是通過(guò)Runnable
來(lái)描述的,不是通過(guò)Thread
自己來(lái)描述的兩種本質(zhì)上差別不大,第二種更利于“解耦和”
這個(gè)Runnable
只是一個(gè)任務(wù),并不與“線程”這樣的概念強(qiáng)相關(guān)
后續(xù)執(zhí)行這個(gè)任務(wù)的載體可以是線程,也可以是其他的東西
[!quote] 協(xié)程、纖程
- 線程是輕量級(jí)進(jìn)程,但進(jìn)程很重
- 隨著對(duì)性能要求的提高,開(kāi)始嫌棄線程,引入協(xié)程
- 也叫做虛擬線程
3. 匿名內(nèi)部類創(chuàng)建 Thread ?類對(duì)象
本質(zhì)是繼承 Thread,和 1 一樣
代碼
package thread; public class Demo3 { public static void main(String[] args) throws InterruptedException { Thread thread = new Thread() { //這里就是在定義匿名內(nèi)部類,這個(gè)類是Thread的子類 public void run() { //在類內(nèi)部重寫(xiě)run方法 while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; thread.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } }
}
匿名內(nèi)部類
[!quote] 匿名內(nèi)部類
- 一般是一次性的,用完就丟了
- 內(nèi)聚性更好一些
- 相關(guān)聯(lián)的代碼放的越集中,內(nèi)聚性越好
Thread thread = new Thread() { //這里就是在定義匿名內(nèi)部類,這個(gè)類是Thread的子類 public void run() { //在類內(nèi)部重寫(xiě)run方法 }
};
- 這一段代碼的解釋
- 定義匿名內(nèi)部類,這個(gè)類是
Thread
的子類 - 類的內(nèi)部,重寫(xiě)了父類的
run
方法 - 創(chuàng)建了一個(gè)子類的實(shí)例,并且把實(shí)例的引用復(fù)制給了
thread
- 定義匿名內(nèi)部類,這個(gè)類是
4.匿名內(nèi)部類創(chuàng)建Runnable?類對(duì)象
本質(zhì)是通過(guò)匿名內(nèi)部類實(shí)現(xiàn)
代碼
package thread; public class Demo4 { public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { while (true) { System.out.println("hello thread"); try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } } }; Thread thread = new Thread(runnable); thread.start(); while (true) { System.out.println("hello main"); Thread.sleep(1000); } }
}
5.lambda 表達(dá)式創(chuàng)建 Runnable ?類對(duì)象
- 本質(zhì)上就是匿名內(nèi)部類,是一個(gè)更簡(jiǎn)化的寫(xiě)法
- 很多時(shí)候,寫(xiě)“匿名內(nèi)部類”,目的不是寫(xiě)“類”,而是為了寫(xiě)那個(gè)
run()
方法 - lambda 可以直接表示我們要寫(xiě)的 run() 方法,省去了一些不需要的部分
public static void main(String[] args) { Thread thread = new Thread(() -> { while(true){ System.out.println("hello thread"); try{ Thread.sleep(1000); }catch(InterruptedException e){ throw new RuntimeException(e); } } }); thread.start(); while(true){ System.out.println("hello main"); try{ Thread.sleep(1000); }catch(InterruptedException e){ throw new RuntimeException(e); } }
}