學(xué)Java的應(yīng)該都知道怕磨,不同于其他大多數(shù)編程語言演痒,Java本身內(nèi)置了多線程的支持始花。多線程讓許多任務(wù)可以同時進(jìn)行妄讯,提高了程序的運(yùn)行效率,帶來了許多便利酷宵,但是許多情況下多線程并不會按照我們所計(jì)劃的進(jìn)行亥贸,也就成了許多程序員的噩夢。那么今天浇垦,我們就來聊聊Java中的線程相關(guān)的內(nèi)容炕置。
本文的要點(diǎn)如下:
- Java線程的基本概念
- 任務(wù)
- Thread類
- 執(zhí)行任務(wù)
- 線程的基本方法
Java線程的基本概念
說到線程男韧,那么就不得不提到另一個常用的概念——線程朴摊。線程和進(jìn)程,總有人傻傻的分不清此虑。
線程是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單位甚纲。而進(jìn)程則是系統(tǒng)進(jìn)行資源分配和調(diào)度的基本單位。簡而言之進(jìn)程就是我們常說的一個應(yīng)用程序朦前,而進(jìn)程中可以包含多個線程分別進(jìn)行不同的工作介杆。
任務(wù)
任務(wù)就是線程中要執(zhí)行的動作。
在Java中的任務(wù)被抽象成了一個Runnable接口:
public interface Runnable {
public void run();
}
我們的自定義任務(wù)需要去實(shí)現(xiàn)這個接口况既,并把任務(wù)的詳細(xì)內(nèi)容寫在覆蓋的run方法里这溅,比如我們定義一個輸出一個字符串的任務(wù):
public class PrintTask implements Runnable {
@Override
public void run() {
System.out.println("輸出一行字");
}
}
Thread類
Java中用Thread類來代表一個線程,而我們常用的是它的這幾種構(gòu)造方法:
Thread(Runnable target, String name)
在創(chuàng)建線程對象的時候傳入需要執(zhí)行的任務(wù)以及這個線程的名稱棒仍。
Thread(Runnable target)
只傳入需要執(zhí)行的任務(wù),名稱是系統(tǒng)自動生成的臭胜,或者可以在創(chuàng)建對象后再通過setName方法修改名稱莫其。
Thread(String name)
只傳入待創(chuàng)建線程的名稱。
Thread()
啥都不傳耸三,就是單純構(gòu)造一個線程對象而已~
執(zhí)行任務(wù)
Thread類的start()方法負(fù)責(zé)開始執(zhí)行一個線程乱陡,讓一個線程運(yùn)行起來有這么兩種方法:
1、創(chuàng)建Thread對象的時候指定需要執(zhí)行的任務(wù):
public class Test {
public static void main(String[] args) {
new Thread(new PrintTask()).start();
}
}
2仪壮、通過繼承Thread類并覆蓋run方法:
Thread類本身就代表了一個Runnable任務(wù)憨颠,我們看Thread類的定義:
public class Thread implements Runnable {
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
// ... 為省略篇幅,省略其他方法和字段
}
其中的target就是在構(gòu)造方法里傳入的,如果構(gòu)造方法不傳這個字段的話爽彤,很顯然run方法就是一個空實(shí)現(xiàn)养盗,所以如果我們想運(yùn)行這個線程,就繼承它并且覆蓋一下run方法适篙。
public class PrintThread extends Thread {
@Override
public void run() {
System.out.println("輸出一行字");
}
}
因?yàn)镻rintThread中已經(jīng)有一個任務(wù)了往核,所以直接調(diào)用start方法運(yùn)行它就好:
public class Test {
public static void main(String[] args) {
new PrintThread().start();
}
}
使用繼承Thread類并且覆蓋run方法的方式把線程和任務(wù)給結(jié)合到了一塊兒了,不可分割了嚷节,也就是所謂的耦合了聂儒,現(xiàn)在都流行低耦合的方式,為了便于修改嘛硫痰。所以我們平時更傾向于使用任務(wù)和線程分開處理的第1種執(zhí)行任務(wù)的方式衩婚。當(dāng)然,有時候?yàn)榱搜菔镜姆奖阈О撸彩菚褂美^承Thread類并且覆蓋run方法的方式非春。
線程基本方法
Thread類提供了許多方法來方便我們獲取線程的信息或者控制線程,下邊來一下都有哪些重要的方法吧:
1.獲取線程ID
long getId():系統(tǒng)會為每個線程自動分配一個long類型的id值鳍悠,通過getId方法可以獲取這個值税娜。
2.獲取和設(shè)置線程名稱
- void setName(String name):設(shè)置線程的名稱。
- String getName():獲取線程的名稱藏研。
3.獲取和設(shè)置線程優(yōu)先級
處理器會從就緒隊(duì)列里挑一個已經(jīng)就緒的線程去執(zhí)行敬矩,每個線程都可以有不同的優(yōu)先級,優(yōu)先級越高蠢挡,越容易被處理器選中去執(zhí)行弧岳。
void setPriority(int newPriority):設(shè)置線程優(yōu)先級。
java中的優(yōu)先級是用一個正數(shù)表示业踏,共有1~10十個等級禽炬,其中,設(shè)計(jì)java的大叔們用了三個靜態(tài)變量表示我們常用的:
- Thread.MIN_PRIORITY = 1
- Thread.NORM_PRIORITY = 5;
- Thread.MAX_PRIORITY = 10;
一般情況下勤家,我們就用這三個變量去表示優(yōu)先級就夠用了~
int getPriority():獲取線程的優(yōu)先級腹尖。
4.休眠
如果想在線程執(zhí)行過程中讓程序停一段時間之后再執(zhí)行,這個停止一段時間也叫做休眠伐脖,就好像睡一段時間然后再醒來热幔。可以通過調(diào)用sleep方法讼庇,來實(shí)現(xiàn)休眠:
static void sleep(long millis) throws InterruptedException
程序在指定的毫秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠绎巨,也就是暫停執(zhí)行。
static void sleep(long millis, int nanos) throws InterruptedException
程序在指定的毫秒數(shù)加納秒數(shù)內(nèi)讓當(dāng)前正在執(zhí)行的線程休眠蠕啄,也就是暫停執(zhí)行场勤。
這個sleep方法是一個靜態(tài)方法,它會讓當(dāng)前線程暫停指定的時間。這個所謂的暫停和媳,或者說休眠其實(shí)只是把正在運(yùn)行的線程阻塞掉格遭,放到阻塞隊(duì)列里,等指定的時間一到窗价,再從阻塞隊(duì)列里出來而已如庭。
5.獲取當(dāng)前正在執(zhí)行的線程
不同的線程可以共享同一個進(jìn)程中的同一段內(nèi)存,也就意味著不同的線程可以執(zhí)行同一段代碼撼港,有些時候有必要獲取一下代碼在哪個線程中執(zhí)行坪它。
static Thread currentThread():獲取當(dāng)前正在執(zhí)行的線程對象的引用。
6.守護(hù)線程
線程可以被分成兩種類型帝牡,一種叫普通線程往毡,另一種就叫守護(hù)線程,守護(hù)線程也被稱為后臺線程靶溜。守護(hù)線程在程序執(zhí)行過程中并不是不可或缺的开瞭,它主要是為普通線程提供便利服務(wù)的,比方說java中不需要我們手動去釋放某個對象的內(nèi)存罩息,它有一種傳說中的垃圾收集器在不停的去釋放程序中不需要的內(nèi)存嗤详,在我們啟動程序的時候系統(tǒng)會幫助我們創(chuàng)建一個負(fù)責(zé)收集垃圾的線程,這個線程就是一個守護(hù)線程瓷炮。如果所有的普通線程全部死掉了葱色,那守護(hù)線程也會跟著死掉,也就是程序就退出了娘香,比方說在所有的普通線程停止了之后苍狰,這個負(fù)責(zé)收集垃圾的守護(hù)線程也會自動退出的。反過來說只要有一個普通線程活著烘绽,程序就不會退出淋昭。我們可以通過下邊這些方法來判斷一個線程是否是守護(hù)線程或者把一個線程設(shè)置為守護(hù)線程:
- boolean isDaemon():判斷該線程是否為守護(hù)線程。
- void setDaemon(boolean on):將該線程標(biāo)記為守護(hù)線程或者普通線程安接。
只有在線程未啟動的時候才能設(shè)置該線程是否為守護(hù)線程翔忽,否則的話會拋出異常。
另外盏檐,從普通線程中生成的線程默認(rèn)是普通線程呀打,從守護(hù)線程中生成的線程默認(rèn)是守護(hù)線程。
7.讓出本次處理器時間片
static void yield():表示放棄此次時間片時間糯笙,等待下次執(zhí)行。
但是撩银,這個yield方法只是建議處理器不要在此次時間片時間內(nèi)繼續(xù)執(zhí)行本線程给涕,最后實(shí)際怎么著還不一定呢,另外,yield是一個靜態(tài)方法够庙,表示讓出當(dāng)前線程本次時間片的時間恭应。
8.等待另一線程執(zhí)行結(jié)束
有的時候需要等待一個線程執(zhí)行完了,另一個線程才能繼續(xù)執(zhí)行耘眨,Thread類提供了這樣的方法:
void join()
等待該線程終止才能繼續(xù)執(zhí)行昼榛。
void join(long millis)
在指定毫秒數(shù)內(nèi)等待該線程終止,如果到達(dá)了指定時間就繼續(xù)之行了剔难。
void join(long millis, int nanos)
跟上邊方法一個意思胆屿,只不過加了個納秒限制。
總結(jié)一下:
- 當(dāng)我們運(yùn)行一個java程序時偶宫,系統(tǒng)會為我們默認(rèn)創(chuàng)建一個名叫main的線程非迹。
- java中的任務(wù)被抽象成了一個Runnable接口,我們的自定義任務(wù)需要去實(shí)現(xiàn)這個接口纯趋,并把任務(wù)的詳細(xì)內(nèi)容寫在覆蓋的run方法里憎兽。
- java中的Thread類來代表一個線程,它提供設(shè)置線程名吵冒、線程要執(zhí)行的任務(wù)等相關(guān)構(gòu)造方法纯命。
- 調(diào)用Thread對象的start方法可以執(zhí)行一個線程,使用某個線程執(zhí)行某個任務(wù)的方式有兩種:
- 創(chuàng)建Thread對象的時候指定需要執(zhí)行的任務(wù)痹栖。
- 通過繼承Thread類并覆蓋run方法亿汞。
- Thread類提供一系列方法來方便我們獲取線程的信息或者控制線程,包括下邊這些功能:
- 獲取線程ID
- 獲取和設(shè)置線程名稱
- 獲取和設(shè)置線程優(yōu)先級
- 線程休眠
- 獲取當(dāng)前正在執(zhí)行的線程
- 守護(hù)線程的設(shè)置和判斷相關(guān)方法
- 讓出本次處理器時間片
- 等待另一線程執(zhí)行結(jié)束
- 可以為某個線程設(shè)置異常處理器來捕獲線程中沒有catch的異常结耀。