詳解Java創(chuàng)建線程的五種常見方式
Java中如何進(jìn)行多線程編程,如何使用多線程?在Java標(biāo)準(zhǔn)庫中提供了一個(gè)Thread類。Java中,一個(gè)進(jìn)程正在運(yùn)行時(shí)至少會(huì)有一個(gè)線程正在運(yùn)行,這些線程在后臺(tái)默默地執(zhí)行,比如調(diào)用main()方法時(shí)就是這樣的,主線程是由JVM創(chuàng)建的。實(shí)現(xiàn)多線程編程的方式主要有兩種,一是繼承Thread類,另一種是實(shí)現(xiàn)Runnable接口。這里我們先來看看Thread類的結(jié)構(gòu):

從源代碼中可以發(fā)現(xiàn),Thread類實(shí)現(xiàn)了Runnable接口,它們之間具有多態(tài)關(guān)系。其實(shí),使用繼承Thread類的方式創(chuàng)建新線程是,最大的局限就是不支持多繼承,因?yàn)樵贘ava語言特點(diǎn)就是單繼承,所以為了支持多繼承,完全可以實(shí)現(xiàn)Runnable接口的方式。
Java中如何創(chuàng)建線程呢?
1.顯示繼承Thread,重寫run來指定現(xiàn)成的執(zhí)行代碼。
代碼
public class Demo1 {
? ? static class MyThread extends Thread {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? System.out.println("hello world, 我是一個(gè)線程");
? ? ? ? ? ? while (true) {
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 創(chuàng)建線程需要使用 Thread 類, 來創(chuàng)建一個(gè) Thread 的實(shí)例.
? ? ? ? // 另一方面還需要給這個(gè)線程指定, 要執(zhí)行哪些指令/代碼.
? ? ? ? // 指定指令的方式有很多種方式, 此處先用一種簡單的, 直接繼承 Thread 類,
? ? ? ? // 重寫 Thread 類中的 run 方法.
? ? ? ? // [注意!] 當(dāng) Thread 對(duì)象被創(chuàng)建出來的時(shí)候, 內(nèi)核中并沒有隨之產(chǎn)生一個(gè)線程(PCB).
? ? ? ? Thread t = new MyThread();
? ? ? ? t.start();
? ? ? ? // 執(zhí)行這個(gè) start 方法, 才是真的創(chuàng)建出了一個(gè)線程.
? ? ? ? // 此時(shí)內(nèi)核中才隨之出現(xiàn)了一個(gè) PCB, 這個(gè) PCB 就會(huì)對(duì)應(yīng)讓 CPU 來執(zhí)行該線程的代碼. (上面的 run 方法中的邏輯)
? ? ? ??
? ? ? ? while (true) {
? ? ? ? ? ? // 這里啥都不干
? ? ? ? }
? ? }
}2.匿名內(nèi)部類繼承Thread,重寫run來執(zhí)行線程執(zhí)行的代碼。
代碼
public class Demo2 {
? ? // Runnable 本質(zhì)上就是描述了一段要執(zhí)行的任務(wù)代碼是啥.
? ? static class MyRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 2. 通過匿名內(nèi)部類的方式繼承 Thread
? ? ? ? Thread t = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t.start();
? ?}3.顯示實(shí)現(xiàn)Runnable接口,重寫run方法。
代碼
public class Demo3 {
? ? // Runnable 本質(zhì)上就是描述了一段要執(zhí)行的任務(wù)代碼是啥.
? ? static class MyRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 3. 顯式創(chuàng)建一個(gè)類, 實(shí)現(xiàn) Runnable 接口, 然后把這個(gè) Runnable 的實(shí)例關(guān)聯(lián)到 Thread 實(shí)例上.
? ? ? ? Thread t = new Thread(new MyRunnable());
? ? ? ? t.start();
? ?}4.匿名內(nèi)部類實(shí)現(xiàn)Runnable接口,重寫run方法
代碼
public class Demo4 {
? ? // Runnable 本質(zhì)上就是描述了一段要執(zhí)行的任務(wù)代碼是啥.
? ? static class MyRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 4. 通過匿名內(nèi)部類來實(shí)現(xiàn) Runnable 接口
? ? ? ? Runnable runnable = new Runnable() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? Thread t = new Thread(runnable);
? ? ? ? t.start();
? ?}5.通過lambda表達(dá)式來描述線程執(zhí)行的代碼
代碼
public class Demo4 {
? ? // Runnable 本質(zhì)上就是描述了一段要執(zhí)行的任務(wù)代碼是啥.
? ? static class MyRunnable implements Runnable {
? ? ? ? @Override
? ? ? ? public void run() {
? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? }
? ? }
? ? public static void main(String[] args) {
? ? ? ? // 5. 使用 lambda 表達(dá)式來指定 線程執(zhí)行的內(nèi)容
? ? ? ? Thread t = new Thread(() -> {
? ? ? ? ? ? System.out.println("我是一個(gè)新線程");
? ? ? ? });
? ? ? ? t.start();
? ?}【面試題】:Thread的run和start之間的區(qū)別?
run()方法::普通的方法調(diào)用,沒有創(chuàng)建新的線程,輸出語句是在原線程中執(zhí)行的。
start()方法::這才是創(chuàng)建了一個(gè)新線程,由新的線程來執(zhí)行輸出
Thread類的具體用法

Thread類常見的一些屬性

ID是現(xiàn)成的唯一標(biāo)識(shí),不同線程不會(huì)重復(fù)
名稱是各種調(diào)試工具會(huì)用到的
狀態(tài)標(biāo)識(shí)線程當(dāng)前所處的一個(gè)情況
優(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 ThreadDemo6 {
? ? public static void main(String[] args) {
? ? ? ? Thread t=new Thread("cxk"){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? for (int i = 0; i < 10; i++) {
? ? ? ? ? ? ? ? ? ? System.out.println(Thread.currentThread().getName());
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(1000);
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? //run方法執(zhí)行過程中就代表著系統(tǒng)內(nèi)線程得生命周期
? ? ? ? //潤方法執(zhí)行中,內(nèi)核的線程就存在
? ? ? ? //run方法執(zhí)行完畢,內(nèi)核中的線程隨之銷毀
? ? ? ? //這一組屬性,只要線程創(chuàng)建完畢,屬性就變了
? ? ? ? System.out.println(t.getName());
? ? ? ? System.out.println(t.getPriority());
? ? ? ? System.out.println(t.isDaemon());
? ? ? ? System.out.println(t.getId());
? ? ? ? //這倆屬性會(huì)隨著現(xiàn)成的運(yùn)行過程而發(fā)生改變
? ? ? ? System.out.println(t.isAlive());
? ? ? ? System.out.println(t.isInterrupted());
? ? ? ? System.out.println(t.getState());
? ? ? ? t.start();
? ? ? ? while (t.isAlive()){
? ? ? ? ? ? System.out.println("cxk線程正在執(zhí)行");
? ? ? ? ? ? System.out.println(t.isInterrupted());
? ? ? ? ? ? System.out.println(t.getState());
? ? ? ? }
? ? }
}執(zhí)行結(jié)果如下:會(huì)出現(xiàn)很多組相同的數(shù)據(jù)

中斷一個(gè)線程
讓一個(gè)線程結(jié)束有兩種情況:
1.此線程已經(jīng)把任務(wù)執(zhí)行完了。即讓線程run完(比較溫和)。
2.此線程將任務(wù)執(zhí)行到一半,被強(qiáng)制結(jié)束。即調(diào)用線程的interrupt()方法,比較激烈。
1.方法一:讓線程run完
這種結(jié)束方式比較溫和,當(dāng)標(biāo)記位被設(shè)置上之后,等到這次循環(huán)執(zhí)行完了之后,在結(jié)束線程,如下,當(dāng)線程執(zhí)行到sleep的時(shí)候,已經(jīng)sleep100ms了,此時(shí)isQuit被設(shè)置為true,當(dāng)前線程不會(huì)立即退出,而是會(huì)繼續(xù)sleep,把剩下的 400ms sleep完才會(huì)結(jié)束這個(gè)線程。
public class ThreadDemo7 {
? ? private static boolean isQuit=false;
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? Thread t=new Thread(){
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? while (!isQuit){
? ? ? ? ? ? ? ? ? ? System.out.println("別煩我,我在忙著轉(zhuǎn)賬呢");
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(500);
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? System.out.println("轉(zhuǎn)賬操作被終止");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t.start();
? ? ? ? Thread.sleep(500);
? ? ? ? //老板來電話了說對(duì)方是內(nèi)鬼終止交易
? ? ? ? System.out.println("有內(nèi)鬼,終止交易!!!");
? ? ? ? isQuit = true;
? ? }
}執(zhí)行結(jié)果:

2.方法二:調(diào)用interrupted()方法
public class ThreadDemo8 {
? ? public static void main(String[] args) throws InterruptedException {
? ? ? ? Thread t = new Thread() {
? ? ? ? ? ? @Override
? ? ? ? ? ? public void run() {
? ? ? ? ? ? ? ? // 此處直接使用線程內(nèi)部的標(biāo)記位來判定.
? ? ? ? ? ? ? ? while (!Thread.currentThread().isInterrupted()) {
? ? ? ? ? ? ? ? ? ? System.out.println("別管我, 我在忙著轉(zhuǎn)賬呢");
? ? ? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? ? ? Thread.sleep(500);
? ? ? ? ? ? ? ? ? ? } catch (InterruptedException e) {
? ? ? ? ? ? ? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? ? ? ? ? ? ? break;
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? System.out.println("轉(zhuǎn)賬被終止.");
? ? ? ? ? ? }
? ? ? ? };
? ? ? ? t.start();
? ? ? ? Thread.sleep(5000);
? ? ? ? System.out.println("對(duì)方是內(nèi)鬼, 快終止交易!!!");
? ? ? ? t.interrupt();
? ? }
}執(zhí)行結(jié)果如下:

在這段代碼中,t.start()是主線程繼續(xù)往下執(zhí)行之后,主線程還是會(huì)繼續(xù)走,新線程則會(huì)執(zhí)行run方法,如果沒有后續(xù)的sleep,新線程能否繼續(xù)輸出就是不確定的了。原因:多線程之間是搶占實(shí)質(zhì)性的,如果主線程中沒有sleep,此時(shí)接下來CPU是執(zhí)行主現(xiàn)成的isQuit=true還是新線程的while循環(huán),這都是不確定的。對(duì)于新線程來說,run方法執(zhí)行完,線程就結(jié)束了。對(duì)于主線程來說main方法執(zhí)行完,住線程就結(jié)束了。
由上可得:
1.通過thread對(duì)象調(diào)用interrupt()方法通知該線程停止運(yùn)行。
2.thread收到通知的方式有兩種:
如果線程調(diào)用了wait/join/sleep等方法而阻塞掛起,則以InterrupterException異常的形式通知,清除中斷標(biāo)志
如果沒有調(diào)用上述方式,就只是內(nèi)部的一個(gè)中斷標(biāo)志被設(shè)置,thread可以通過Thread.interrupted()判斷當(dāng)前線程的中斷標(biāo)志被設(shè)置,來清除中斷標(biāo)志。也可以
使用Thread.currentThread().isInterrupted()判斷指定線程的中斷標(biāo)志被設(shè)置,但是不會(huì)清除中斷標(biāo)志。
在Java中第二種方式通知收到的更及時(shí),即使線程正在sleep也可以馬上收到。
public class ThreadDemo10 {
public static void main(String[] args) {
Thread t=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().isInterrupted());
//僅僅是判定標(biāo)記位,不會(huì)修改標(biāo)記位
}
}
};
t.start();
t.interrupt();
}
}
public class ThreadDemo11 {
public static void main(String[] args) throws InterruptedException {
Thread t=new Thread(){
@Override
public void run() {
System.out.println("我是新線程");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t.start();
while (true){
System.out.println("我是主線程");
Thread.sleep(1000);
//對(duì)于新線程來說,run方法執(zhí)行完新線程就結(jié)束了,
//對(duì)于主線程來說,main方法執(zhí)行完主線程就結(jié)束了
}
}
}
線程等待
線程之間是并發(fā)執(zhí)行的關(guān)系,多個(gè)線程之間,誰先執(zhí)行,誰后執(zhí)行,誰執(zhí)行到何處讓出CPU…開發(fā)人員是完全無法感知的,全權(quán)由系統(tǒng)內(nèi)核負(fù)責(zé),例如,創(chuàng)建一個(gè)新線程的時(shí)候,此時(shí)接下來是主線程繼續(xù)執(zhí)行,還是新線程執(zhí)行,這個(gè)事情是不能保證的,這就是“搶占式”執(zhí)行的重要特點(diǎn)。這時(shí)候就引入了線程等待:開發(fā)人員可以控制哪個(gè)線程先結(jié)束,哪個(gè)線程后結(jié)束。join()方法的執(zhí)行就會(huì)讓線程阻塞,一直阻塞到對(duì)應(yīng)線程執(zhí)行結(jié)束之后,才會(huì)繼續(xù)執(zhí)行。這就可以控制線程結(jié)束的先后順序。如果線程結(jié)束了才調(diào)用到j(luò)oin,此時(shí)也會(huì)立刻返回。
public class ThreadDemo12 {
public static void main(String[] args) throws InterruptedException {
Thread t1=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是線程1");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
Thread t2=new Thread(){
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("我是線程2");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
t1.start();
t2.start();
t1.join();// join 起到的效果是等待線程結(jié)束. 當(dāng)執(zhí)行到這行代碼是, 程序就阻塞了. 一直阻塞到 t1 結(jié)束, 才會(huì)繼續(xù)執(zhí)行.
t2.join();
System.out.println("主線程執(zhí)行完畢");
}
}
執(zhí)行結(jié)果如下:

線程休眠

當(dāng)線程在正常運(yùn)行計(jì)算判斷邏輯此時(shí)就是在就緒隊(duì)列中排隊(duì),調(diào)度器就會(huì)從就緒隊(duì)列中篩選出合適的PCB讓他上CPU執(zhí)行,如果某個(gè)線程調(diào)用sleep就會(huì)讓對(duì)應(yīng)的線程PCB進(jìn)入到阻塞隊(duì)列,線程一旦進(jìn)入到了阻塞隊(duì)列是沒有辦法上CPU執(zhí)行的,對(duì)于sleep進(jìn)入冷宮的時(shí)間是有限制的,時(shí)間到了之后,就自動(dòng)被系統(tǒng)把這個(gè)PCB那回到原來的就緒隊(duì)列中了。
線程的狀態(tài)轉(zhuǎn)換

以上就是詳解Java創(chuàng)建線程的五種常見方式的詳細(xì)內(nèi)容,更多關(guān)于Java創(chuàng)建線程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Java利用MultipartFile實(shí)現(xiàn)上傳多份文件的代碼
這篇文章主要介紹了Java利用MultipartFile實(shí)現(xiàn)上傳多份文件的代碼,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-09-09
詳解如何在Spring Boot啟動(dòng)后執(zhí)行指定代碼
這篇文章主要介紹了在Spring Boot啟動(dòng)后執(zhí)行指定代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-06-06
MultipartResolver實(shí)現(xiàn)文件上傳功能
這篇文章主要為大家詳細(xì)介紹了MultipartResolver實(shí)現(xiàn)文件上傳功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-06-06
多數(shù)據(jù)源如何實(shí)現(xiàn)事務(wù)管理
Spring中涉及三個(gè)核心事務(wù)處理接口:PlatformTransactionManager、TransactionDefinition和TransactionStatus,PlatformTransactionManager提供事務(wù)操作的基本方法,如獲取事務(wù)、提交和回滾2024-09-09
springboot CommandLineRunner接口實(shí)現(xiàn)自動(dòng)任務(wù)加載功能
這篇文章主要介紹了springboot CommandLineRunner接口實(shí)現(xiàn)自動(dòng)任務(wù)加載功能,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-05-05
全局記錄Feign的請(qǐng)求和響應(yīng)日志方式
這篇文章主要介紹了全局記錄Feign的請(qǐng)求和響應(yīng)日志方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
MyBatisCodeHelperPro插件下載及使用教程詳解
這篇文章主要介紹了MyBatis CodeHelperPro插件使用教程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09

