詳解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)文章
springboot集成springsecurity 使用OAUTH2做權(quán)限管理的教程
這篇文章主要介紹了springboot集成springsecurity 使用OAUTH2做權(quán)限管理的教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-12-12logstash將mysql數(shù)據(jù)同步到elasticsearch方法詳解
這篇文章主要為大家介紹了logstash將mysql數(shù)據(jù)同步到elasticsearch方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12java實(shí)現(xiàn)抽獎(jiǎng)功能解析
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)抽獎(jiǎng)功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03java設(shè)計(jì)模式之工廠模式實(shí)例詳解
這篇文章主要介紹了java設(shè)計(jì)模式之工廠模式,結(jié)合具有實(shí)例形式分析了java工廠模式的概念、原理、實(shí)現(xiàn)與使用方法,需要的朋友可以參考下2017-09-09如何解決項(xiàng)目中java heap space的問題
這篇文章主要介紹了如何解決項(xiàng)目中java heap space的問題,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07使用TraceId在Spring Cloud中實(shí)現(xiàn)線上問題快速定位
在微服務(wù)架構(gòu)中,服務(wù)間的互相調(diào)用使得問題定位變得復(fù)雜,在此背景下,TraceId為我們提供了一個(gè)在復(fù)雜環(huán)境中追蹤請(qǐng)求路徑和定位問題的工具,本文不僅介紹TraceId的基本概念,還將結(jié)合真實(shí)場(chǎng)景,為您展示如何在Spring Cloud中應(yīng)用它2023-09-09Java Swing中JDialog實(shí)現(xiàn)用戶登陸UI示例
這篇文章主要介紹了Java Swing中JDialog實(shí)現(xiàn)用戶登陸UI功能,結(jié)合完整實(shí)例形式分析了Swing使用JDialog實(shí)現(xiàn)用戶登陸UI界面窗口功能的步驟與相關(guān)操作技巧,需要的朋友可以參考下2017-11-11SpringBoot利用隨機(jī)鹽值實(shí)現(xiàn)密碼的加密與驗(yàn)證
這篇文章主要為大家詳細(xì)介紹了SpringBoot如何利用隨機(jī)鹽值實(shí)現(xiàn)密碼的加密與驗(yàn)證,文中的示例代碼講解詳細(xì),有需要的小伙伴可以參考下2024-02-02Springboot中如何使用Redisson實(shí)現(xiàn)分布式鎖淺析
redisson是redis的java客戶端程序,國內(nèi)外很多公司都有在用,下面這篇文章主要給大家介紹了關(guān)于Springboot中如何使用Redisson實(shí)現(xiàn)分布式鎖的相關(guān)資料,需要的朋友可以參考下2021-10-10