本文概述
本篇文章將分四塊內(nèi)容對Java中的多線程機制進行介紹:
一. 多線程概述
二. 實現(xiàn)多線程的兩種方式
三. 多線程的生命周期
四. 線程調(diào)度和控制
一. 線程與進程的概述
??線程是依賴于進程而存在的圈驼,因此在討論線程之前吓揪,我們必須要知道什么是進程
1. 什么是進程
??進程就是正在運行的程序,是系統(tǒng)進行資源分配和調(diào)用的獨立單位充包。每一個進程都有它自己的內(nèi)存空間和系統(tǒng)資源。當我們打開電腦資源管理器時帆疟,就可以顯示當前正在運行的所有進程粟按。
2. 多進程的意義
??大家應該有過這樣的經(jīng)歷:我可以同時在電腦上做很多事情,比如我可以一邊玩游戲遏餐,一邊聽音樂伦腐,網(wǎng)速夠快我還可以同時用迅雷下載電影赢底。這是因為我們的操作系統(tǒng)支持多進程失都,簡而言之就是:能在同一時段內(nèi)執(zhí)行多個任務
??需要注意的是,對于單核計算機來講幸冻,游戲和聽音樂這兩個任務并不是“同時進行”的粹庞,因為CPU在某個時間點上只能做一件事情,計算機是在游戲進程和音樂進程間做著頻繁切換洽损,且切換速度很快(也就是輪流執(zhí)行CPU時間片)庞溜,所以,我們感覺游戲和音樂好像在“同時”進行,其實并不是同時執(zhí)行的流码。
3. 什么是多線程
??在一個進程內(nèi)部又可以執(zhí)行多個任務,而這每一個任務我們就可以看成是一個線程漫试。
??下面是一段代碼示例:
public class Demo {
public static void main(String args[]) {
//代碼段1
do();
//代碼段2
}
public static void do() {
//代碼段11
function1();
function2();
//代碼段22
}
public static void function1(){...}
public static void function2(){...}
}
如果是單線程的執(zhí)行方式:是一條執(zhí)行路徑
如果是多線程的執(zhí)行方式:有多條執(zhí)行路徑
- 是進程中的單個順序控制流六敬,是一條執(zhí)行路徑。
- 一個進程如果只有一條執(zhí)行路徑驾荣,則稱為單線程程序外构。
- 一個進程如果有多條執(zhí)行路徑,則稱為多線程程序播掷。
4. 多進程的意義
??多線程的作用不是提高執(zhí)行速度审编,而是為了提高應用程序的使用率。
并行:邏輯上同時發(fā)生歧匈,指在某一段時間段同時運行多段程序
并發(fā):物理上同時發(fā)生垒酬,指在某一個時間點同時運行多段程序
??多線程卻給了我們一個錯覺:讓我們認為多個線程是并發(fā)執(zhí)行的。其實不是:多個線程共享同一個進程的資源眯亦,但是棧內(nèi)存是獨立的伤溉,一個線程一個棧。所以他們?nèi)匀皇窃趽孋PU的資源執(zhí)行妻率。一個時間點上只有能有一個線程執(zhí)行乱顾。而且誰搶到,這個不一定宫静,所以走净,造成了線程運行的隨機性。其中的某一個進程如果執(zhí)行路徑比較多的話孤里,就會有更高的幾率搶到CPU的執(zhí)行權(quán)伏伯。
5. Java中的多線程
??Java程序運行會啟動JVM,相當于啟動了一個進程捌袜,該進程會自動啟動一個 “主線程” 说搅,而main方法就運行在這個主線程當中,所以我們之前寫的程序都是單線程虏等。
思考:JVM啟動是單線程還是多線程弄唧?
答案:多線程,JVM一定會啟動主線程和垃圾處理機制霍衫,所以一定是多線程
二. 實現(xiàn)多線程的兩種方式
1. 多線程實現(xiàn)方式V1.0(繼承Thread類)
根據(jù)API文檔查詢可以得到創(chuàng)建多線程的方法:
- 自定義類繼承Thread類
- 該子類重寫子類的run()方法
- 并啟動該子類的實例候引。
- 調(diào)用實例的start方法
放在run方法中的代碼會被線程執(zhí)行
注意:run()方法實際上是單線程,不能直接調(diào)用run()方法敦跌,要先看到多線程的效果澄干,必須使用start()方法蔚润。run()僅僅只是被封裝的執(zhí)行代碼裸弦,而是普通方法辐啄,然而start方法會先啟用線程吵瞻,然后再用jvm去調(diào)用線程的run方法
因此:要啟動多線程,一定要調(diào)用start()方法从媚,不能使用run()方法
子線程1(Thread1):
public class FirstThread extends Thread{
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("Thread1\t" + i);
}
}
}
子線程2(Thread2):
public class SecondThread extends Thread{
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("Thread2\t" + i);
}
}
}
main函數(shù)
public class MyThreadDemo {
public static void main(String args[]) {
Thread t1 = new FirstThread();
Thread t2 = new SecondThread();
t1.start();
t2.start();
}
}
2. 多線程實現(xiàn)方式V2.0(實現(xiàn)Runnable接口)
- 自定義類MyRunnerble()實現(xiàn)Runnable接口
- 在MyRunnerble中重寫run()方法
- 創(chuàng)建MyRunnerble的實例對象
- 將所創(chuàng)建的對象作為參數(shù)傳入到Thread當中
public class MyThreadDemo2 {
public static void main(String args[]) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
System.out.println("thread1" + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 0;i < 100; i++) {
System.out.println("thread2" + i);
}
}
});
t1.start();
t2.start();
}
}
3. 兩種創(chuàng)建方式的比較
??既然有了方式一搂誉,為什么又要有方式二呢?
??比較兩種創(chuàng)建方式静檬,我們不難發(fā)現(xiàn)炭懊,第一種方式是通過繼承的方式實現(xiàn)的,第二種方式是通過接口的方式實現(xiàn)拂檩。
繼承Runnerble接口的優(yōu)點:
??1. 可以避免Java單繼承帶來的局限性侮腹。
??2. 適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼稻励,數(shù)據(jù)有效的分離父阻,較好體現(xiàn)了面向?qū)ο蟮脑O(shè)計思想。
三. 多線程的生命周期
??多線程的生命周期如下圖所示:
四. 線程調(diào)度和控制
1. 線程調(diào)度
??如果我們的計算機只有一個 CPU望抽,那么 CPU 在某一個時刻只能執(zhí)行一條指令加矛,線程只有得到 CPU時間片,也就是使用權(quán)煤篙,才可以執(zhí)行指令斟览。那么Java是如何對線程進行調(diào)用的呢?
線程調(diào)度的兩種模式
- 分時調(diào)度模式:所有線程輪流使用 CPU 的使用權(quán)辑奈,平均分配每個線程占用 CPU 的時間片苛茂。
- 搶占式調(diào)度模型:優(yōu)先讓優(yōu)先級高的線程使用 CPU,如果線程的優(yōu)先級相同鸠窗,那么會隨機選擇一個妓羊,優(yōu)先級高的線程獲取的 CPU 時間片相對多一些。
Java采用的是搶占式調(diào)度模式稍计,用優(yōu)先級控制時間片輪轉(zhuǎn)躁绸。
設(shè)置和獲取優(yōu)先級的方式如下:
public final int getPriority()
public final void setPriority(int newPriority)
2. 線程的控制
2.1 線程休眠
??相當于在線程中暫停了幾秒
方法:
public static void sleep(long millis)
示例
public class SecondThread extends Thread{
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程2");
}
}
注意
該方法是靜態(tài)方法,可以通過類直接調(diào)用臣嚣,而且會拋出異常净刮。
2.2 線程加入
為了讓某些需要執(zhí)行的線程執(zhí)行完畢,別的線程才能拿夠繼續(xù)
方法
public final void join()
示例
public class MyThreadDemo {
public static void main(String args[]) {
Thread t1 = new FirstThread();
Thread t2 = new SecondThread();
t1.start();
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
注意
只有當t1線程執(zhí)行完畢之后才會執(zhí)行第二個線程茧球,要注意該方法也會拋出異常庭瑰。
2.3 線程禮讓
暫停執(zhí)行當前線程星持,并執(zhí)行其他線程抢埋,在一定的程度上能夠交替執(zhí)行,但不能保證一定是交替執(zhí)行的
方法
public static void yield()
注意
使用方法與之前類似,該方法是靜態(tài)方法揪垄,所以直接可以通過類調(diào)用
2.4 后臺線程(守護線程)
將該線程標記為守護線程或者是用戶線程穷吮,當正在運行的線程都是守護線程時,java虛擬機退出饥努。
方法
public final void setDaemon(boolean on)
示例:
public class MyThreadDemo {
public static void main(String args[]) {
Thread t1 = new FirstThread();
Thread t2 = new SecondThread();
Thread t3 = new ThirdThread();
t1.setDaemon(true);//將t1設(shè)置為守護進程
t2.setDaemon(true);//將t2設(shè)置為守護進程
t1.start();
t2.start();
t3.start();
}
}
注意:
該方法只能夠在開啟線程之前使用捡鱼,而且不能立即停止,有一定的延遲酷愧。
2.5 線程中斷
中途關(guān)閉線程
方法
public final void stop()//過時了驾诈,但是還是可以使用的,不安全不建議使用
public void interrupt()//他讓我們拋出一個異常溶浴,如果受阻乍迄,那么狀態(tài)終止,拋出異常
總結(jié)
??多線程是Java各種機制中非常重要也是比較陌生的一塊內(nèi)容士败,需要對計算機操作系統(tǒng)運行機制有一定了解的情況下才能深入理解闯两,之后的文章會對多線程的安全,死鎖和與線程有關(guān)的設(shè)計模式做更深入的介紹谅将,歡迎繼續(xù)觀看后續(xù)內(nèi)容漾狼,一起體會Java語言的智慧與魅力。