0. 序言
- Java并發(fā)付材,既然重點(diǎn)朦拖,亦是難點(diǎn)。
- 并發(fā)厌衔,即多線程璧帝,指多個(gè)線程同一時(shí)間去做多個(gè)事情,示例:炒菜哥富寿,一人同時(shí)炒7個(gè)菜睬隶。
- 并發(fā)相對(duì)的是單線程,指一件事情做完才做其他事情页徐,示例:炒完一個(gè)菜再去炒第二個(gè)菜苏潜。
- 并發(fā)不同于并行,并行指的是兩個(gè)或更多任務(wù)同時(shí)進(jìn)行变勇,而并發(fā)指的兩個(gè)任務(wù)都請(qǐng)求執(zhí)行恤左,而處理只能接受一個(gè)任務(wù),就把兩個(gè)任務(wù)安排輪流執(zhí)行搀绣,由于時(shí)間間隔較短飞袋,使人感覺兩個(gè)任務(wù)都在運(yùn)行。
- 本片博文的主要內(nèi)容有:
- 創(chuàng)建線程常用的三種方式
- 線程的生命周期
- 線程的加入
- 線程的中斷
- 線程的禮讓
- 線程的優(yōu)先級(jí)
1. 創(chuàng)建線程常用的三種方式
- 繼承Thread類
public class Main {
public static void main(String[] args) {
new ThreadTest().start();
}
public static class ThreadTest extends Thread{
private int count = 10;
public void run(){
while (true){
System.out.print(count+" ");
if (--count==0){
return;
}
}
}
}
}
說明:步驟:
① 定義類繼承Thread
② 重寫run方法
③ 把新線程要做的事寫在run方法中
④ 創(chuàng)建線程對(duì)象链患,并自動(dòng)執(zhí)行run方法巧鸭。
- 實(shí)現(xiàn)Runnable接口
如果需要繼承其他類(非Thread類),而且還要使當(dāng)前類實(shí)現(xiàn)多線程麻捻,那么可以通過Runnable接口來實(shí)現(xiàn)纲仍。
public class Main {
public static void main(String[] args) {
Runnable runnable = new ThreadTest();
Thread thread = new Thread(runnable);
thread.start();
}
public static class ThreadTest implements Runnable{
private int count = 10;
public void run(){
while (true){
System.out.print(count+" ");
if (--count==0){
return;
}
}
}
}
}
說明:步驟:
① 定義類實(shí)現(xiàn)Runnable接口
② 重寫run方法
③ 將要寫的代碼寫在run方法中
④ 使用參數(shù)為Runnable對(duì)象的構(gòu)造方法創(chuàng)建Thread實(shí)例
⑤ 調(diào)用start方法啟動(dòng)線程
- 實(shí)現(xiàn)Callable接口
Callable接口屬于Executor框架中的功能類呀袱,Callable接口與Runnable接口的功能類似,但提供了比Runnable更強(qiáng)大的功能郑叠,主要表現(xiàn)為以下3點(diǎn):
① Callable可以在任務(wù)結(jié)束后提供一個(gè)返回值夜赵,Runnable無法提供這個(gè)功能。
② Callable中的call()方法可以拋出異常锻拘,而Runnable的run()方法不能拋出異常油吭。
③ 運(yùn)行Callable可以拿到一個(gè)Future對(duì)象,F(xiàn)uture對(duì)象表示異步計(jì)算的結(jié)果署拟,它提供了檢查計(jì)算是否完成的方法婉宰。由于線程屬于異步計(jì)算模型,因此無法從別的線程中得到函數(shù)的返回值推穷,在這種情況下就可以使用Future來監(jiān)視目標(biāo)線程調(diào)用Call()方法的情況心包。但調(diào)用Future的get()方法以獲取結(jié)果時(shí),當(dāng)前線程就會(huì)阻塞馒铃,直到Call()方法返回結(jié)果蟹腾。
public class Test implements Callable {
public static void main(String[] args) {
Test test = new Test();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future submit = executorService.submit(test);
try {
Object o = submit.get();
System.out.println(o);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}finally {
executorService.shutdown();
}
}
@Override
public Boolean call() throws Exception {
return true;
}
}
true
說明:這里的情況還需要后續(xù)的文章進(jìn)行補(bǔ)充,通過這個(gè)小例子区宇,直到通過Callable接口可以在任務(wù)結(jié)束后得到一個(gè)返回值即可娃殖。
2. 線程的生命周期
- 出生狀態(tài):New
Thread thread1 = new Thread(instance);
- 就緒狀態(tài)(可執(zhí)行狀態(tài)):Ready
thread1.start();
說明:用戶調(diào)用start()方法后,線程就處于就緒狀態(tài)议谷。此時(shí)并沒有得到系統(tǒng)資源炉爆。
- 運(yùn)行狀態(tài):Runnable
說明:當(dāng)線程得到系統(tǒng)資源后就進(jìn)入運(yùn)行狀態(tài)。 - 等待狀態(tài):Wating
try {
thread1.wait();
} catch (InterruptedException e) {
e
說明:等待狀態(tài)的線程必須調(diào)用Thread類中的nofify()方法才能被喚醒卧晓;notifyAll()方法是將所有處于等待狀態(tài)下的線程喚醒芬首。
- 休眠狀態(tài)(超時(shí)等待狀態(tài)):Time waiting
Thread.sleep(5000);
- 阻塞狀態(tài):Blocked
說明:當(dāng)一個(gè)線程在運(yùn)行狀態(tài)下發(fā)出輸入/輸出請(qǐng)求,該線程進(jìn)入阻塞狀態(tài)逼裆;在其等待輸入/輸出結(jié)束時(shí)線程進(jìn)入就緒狀態(tài)郁稍,對(duì)于阻塞的線程來說,即使系統(tǒng)資源空閑胜宇,線程依然不能回到運(yùn)行狀態(tài)耀怜。 - 終止?fàn)顟B(tài)(死亡狀態(tài)):Terminated
說明:有兩種情況,分別是
① 當(dāng)線程的run方法執(zhí)行完畢時(shí)桐愉。
② 因?yàn)橐粋€(gè)沒有捕獲的異常而終止了run方法封寞。 - 使線程處于就緒狀態(tài)的方法
① 調(diào)用sleep()方法
② 調(diào)用wati()方法
③ 等待輸入輸出完成 - 使線程處于運(yùn)行狀態(tài)的方法
① 線程調(diào)用notify()方法
② 線程調(diào)用notifyAll()方法
③ 線程調(diào)用interrupt()方法
④ 線程的休眠時(shí)間結(jié)束
⑤ 輸入/輸出結(jié)束
3. 線程的加入
- 舉例說明:
public class Test_Join {
private static int A = 0;
private static int B = 0;
private Thread thread01;
private Thread thread02;
public Test_Join() {
thread02 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (B == 99) {
System.out.println("B==99");
}
B++;
}
});
thread01 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (A == 99) {
System.out.println("A == 99");
}
A++;
// try {
// thread02.join();
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
});
}
private Thread getThread01() {
return thread01;
}
private Thread getThread02() {
return thread02;
}
public static void main(String[] args) {
new Test_Join().getThread01().start();
new Test_Join().getThread02().start();
}
}
說明:
① 兩個(gè)線程分別給A或B++的操作,線程01先執(zhí)行仅财,應(yīng)該是線程01先到99。
② 添加thread02.join();以后碗淌,線程1在執(zhí)行了一個(gè)A++以后盏求,線程2就開始執(zhí)行抖锥,直到執(zhí)行結(jié)束,線程1才執(zhí)行碎罚。
4. 線程的中斷
- 布爾值標(biāo)記的運(yùn)用
public class Test_Interrupt implements Runnable {
private static boolean isContinue = false;
private static int num = 0;
static Test_Interrupt runnable = new Test_Interrupt();
@Override
public void run() {
while (true){
if (isContinue){
break;
}else{
num++;
}
}
}
private static void setContinue(){
isContinue = true;
}
public static void main(String[] args) {
Thread thread = new Thread(runnable);
thread.start();
try {
Thread.sleep(50);
setContinue();
System.out.println(num);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
說明:以往有的時(shí)候會(huì)使用stop()方法來停止線程磅废,但現(xiàn)在的JDK早就廢除了stop()方法,不建議使用【A遥現(xiàn)在提倡在run()方法中使用無限循環(huán)的形式拯勉,然后使用一個(gè)布爾型標(biāo)記控制循環(huán)的停止。
- interrupt()方法的運(yùn)用
如果線程是因?yàn)槭褂昧藄leep()或wait()方法進(jìn)入了就緒狀態(tài)憔购,可以使用Thread類的interrupt()方法使線程離開run()方法宫峦,同時(shí)結(jié)束線程,但程序會(huì)拋出InterruptedException異常玫鸟,用戶可以在處理該異常時(shí)完成線程的中斷業(yè)務(wù)處理导绷,如終止while循環(huán)。在項(xiàng)目中經(jīng)常在這里執(zhí)行關(guān)閉數(shù)據(jù)庫連接和關(guān)閉Socket連接等操作屎飘。
public class Test_Interrupt implements Runnable {
private static boolean isContinue = false;
private static int num = 0;
static Test_Interrupt runnable = new Test_Interrupt();
@Override
public void run() {
while (true) {
if (isContinue) {
break;
} else {
num++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
System.out.println("線程被中斷");
setContinue();
}
}
}
}
private static void setContinue() {
isContinue = true;
}
public static void main(String[] args) {
Thread thread = new Thread(runnable);
thread.start();
thread.interrupt();
}
}
說明:由于調(diào)用了interrupt()方法妥曲,所以拋出了InterruptedException異常,在異常中我們關(guān)閉while循環(huán)钦购。
5. 線程的禮讓
- Thread類提供了一共禮讓方法yield()表示檐盟,不過它只是給當(dāng)前正處于運(yùn)行狀態(tài)下的線程一個(gè)提醒,告知它可以將資源禮讓給其他線程押桃,但這僅僅只是一個(gè)暗示葵萎,沒有任何一種機(jī)制保證當(dāng)前線程會(huì)將資源禮讓。
- 對(duì)于支持多任務(wù)的操作系統(tǒng)來說怨规,不需要調(diào)用yield()方法陌宿,因?yàn)椴僮飨到y(tǒng)會(huì)為線程自動(dòng)分配CPU時(shí)間片來執(zhí)行。
6. 線程的優(yōu)先級(jí)
- 現(xiàn)在操作系統(tǒng)采用時(shí)分的形式調(diào)度運(yùn)行的線程波丰,操作系統(tǒng)會(huì)分出一個(gè)個(gè)時(shí)間片壳坪,線程會(huì)分配到若干時(shí)間片,當(dāng)線程的時(shí)間片用完了就會(huì)發(fā)生線程調(diào)度掰烟,并等待著下次分配爽蝴。線程分配到的時(shí)間片多少也決定了線程使用處理器資源的多少,而線程的優(yōu)先級(jí)就是決定線程需要多或者少分配一些處理器資源的線程屬性纫骑。
- 在Java線程中蝎亚,通過整型成員變量priority來控制優(yōu)先級(jí),范圍是1~10先馆,線程創(chuàng)建的時(shí)候用setPriority(int)方法來修改優(yōu)先級(jí)发框,默認(rèn)優(yōu)先級(jí)是5,優(yōu)先級(jí)高的線程分配時(shí)間片的數(shù)量要多于優(yōu)先級(jí)低的線程煤墙。針對(duì)頻繁阻塞(休眠或者I/O操作)的線程需要設(shè)置較高優(yōu)先級(jí)梅惯,而偏重計(jì)算(需要較多CPU時(shí)間或者偏運(yùn)算)的線程則設(shè)置澆地的優(yōu)先級(jí)宪拥,確保處理器不會(huì)被獨(dú)占。
- 然而在不同的JVM以及操作系統(tǒng)上铣减,線程規(guī)劃會(huì)存在差異她君,有些操作系統(tǒng)甚至?xí)雎詫?duì)線程優(yōu)先級(jí)的設(shè)定。所以程序正確性不能依賴線程的優(yōu)先級(jí)高低葫哗。
7. 后續(xù)
如果大家喜歡這篇文章缔刹,歡迎點(diǎn)贊!
如果想看更多 Java并發(fā) 方面的技術(shù)劣针,歡迎關(guān)注!