詳解Java創(chuàng)建線程的五種常見方式
Java中如何進(jìn)行多線程編程,如何使用多線程?在Java標(biāo)準(zhǔn)庫中提供了一個Thread類。Java中,一個進(jìn)程正在運(yùn)行時至少會有一個線程正在運(yùn)行,這些線程在后臺默默地執(zhí)行,比如調(diào)用main()方法時就是這樣的,主線程是由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, 我是一個線程"); ? ? ? ? ? ? while (true) { ? ? ? ? ? ? } ? ? ? ? } ? ? } ? ? public static void main(String[] args) { ? ? ? ? // 創(chuàng)建線程需要使用 Thread 類, 來創(chuàng)建一個 Thread 的實(shí)例. ? ? ? ? // 另一方面還需要給這個線程指定, 要執(zhí)行哪些指令/代碼. ? ? ? ? // 指定指令的方式有很多種方式, 此處先用一種簡單的, 直接繼承 Thread 類, ? ? ? ? // 重寫 Thread 類中的 run 方法. ? ? ? ? // [注意!] 當(dāng) Thread 對象被創(chuàng)建出來的時候, 內(nèi)核中并沒有隨之產(chǎn)生一個線程(PCB). ? ? ? ? Thread t = new MyThread(); ? ? ? ? t.start(); ? ? ? ? // 執(zhí)行這個 start 方法, 才是真的創(chuàng)建出了一個線程. ? ? ? ? // 此時內(nèi)核中才隨之出現(xiàn)了一個 PCB, 這個 PCB 就會對應(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("我是一個新線程"); ? ? ? ? } ? ? } ? ? 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("我是一個新線程"); ? ? ? ? } ? ? } ? ? public static void main(String[] args) { ? ? ? ? // 3. 顯式創(chuàng)建一個類, 實(shí)現(xiàn) Runnable 接口, 然后把這個 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("我是一個新線程"); ? ? ? ? } ? ? } ? ? public static void main(String[] args) { ? ? ? ? // 4. 通過匿名內(nèi)部類來實(shí)現(xiàn) Runnable 接口 ? ? ? ? Runnable runnable = new Runnable() { ? ? ? ? ? ? @Override ? ? ? ? ? ? public void run() { ? ? ? ? ? ? ? ? System.out.println("我是一個新線程"); ? ? ? ? ? ? } ? ? ? ? }; ? ? ? ? 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("我是一個新線程"); ? ? ? ? } ? ? } ? ? public static void main(String[] args) { ? ? ? ? // 5. 使用 lambda 表達(dá)式來指定 線程執(zhí)行的內(nèi)容 ? ? ? ? Thread t = new Thread(() -> { ? ? ? ? ? ? System.out.println("我是一個新線程"); ? ? ? ? }); ? ? ? ? t.start(); ? ?}
【面試題】:Thread的run和start之間的區(qū)別?
run()方法::普通的方法調(diào)用,沒有創(chuàng)建新的線程,輸出語句是在原線程中執(zhí)行的。
start()方法::這才是創(chuàng)建了一個新線程,由新的線程來執(zhí)行輸出
Thread類的具體用法
Thread類常見的一些屬性
ID是現(xiàn)成的唯一標(biāo)識,不同線程不會重復(fù)
名稱是各種調(diào)試工具會用到的
狀態(tài)標(biāo)識線程當(dāng)前所處的一個情況
優(yōu)先級高的線程理論上來說更容易被調(diào)度到
關(guān)于后臺先后曾,需要記住一點(diǎn):JVM會在一個進(jìn)程的所有非后臺線程結(jié)束后,才會結(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()); ? ? ? ? //這倆屬性會隨著現(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é)果如下:會出現(xiàn)很多組相同的數(shù)據(jù)
中斷一個線程
讓一個線程結(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的時候,已經(jīng)sleep100ms了,此時isQuit被設(shè)置為true,當(dāng)前線程不會立即退出,而是會繼續(xù)sleep,把剩下的 400ms sleep完才會結(jié)束這個線程。
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); ? ? ? ? //老板來電話了說對方是內(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("對方是內(nèi)鬼, 快終止交易!!!"); ? ? ? ? t.interrupt(); ? ? } }
執(zhí)行結(jié)果如下:
在這段代碼中,t.start()是主線程繼續(xù)往下執(zhí)行之后,主線程還是會繼續(xù)走,新線程則會執(zhí)行run方法,如果沒有后續(xù)的sleep,新線程能否繼續(xù)輸出就是不確定的了。原因:多線程之間是搶占實(shí)質(zhì)性的,如果主線程中沒有sleep,此時接下來CPU是執(zhí)行主現(xiàn)成的isQuit=true還是新線程的while循環(huán),這都是不確定的。對于新線程來說,run方法執(zhí)行完,線程就結(jié)束了。對于主線程來說main方法執(zhí)行完,住線程就結(jié)束了。
由上可得:
1.通過thread對象調(diào)用interrupt()方法通知該線程停止運(yùn)行。
2.thread收到通知的方式有兩種:
如果線程調(diào)用了wait/join/sleep等方法而阻塞掛起,則以InterrupterException異常的形式通知,清除中斷標(biāo)志
如果沒有調(diào)用上述方式,就只是內(nèi)部的一個中斷標(biāo)志被設(shè)置,thread可以通過Thread.interrupted()判斷當(dāng)前線程的中斷標(biāo)志被設(shè)置,來清除中斷標(biāo)志。也可以
使用Thread.currentThread().isInterrupted()判斷指定線程的中斷標(biāo)志被設(shè)置,但是不會清除中斷標(biāo)志。
在Java中第二種方式通知收到的更及時,即使線程正在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)記位,不會修改標(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); //對于新線程來說,run方法執(zhí)行完新線程就結(jié)束了, //對于主線程來說,main方法執(zhí)行完主線程就結(jié)束了 } } }
線程等待
線程之間是并發(fā)執(zhí)行的關(guān)系,多個線程之間,誰先執(zhí)行,誰后執(zhí)行,誰執(zhí)行到何處讓出CPU…開發(fā)人員是完全無法感知的,全權(quán)由系統(tǒng)內(nèi)核負(fù)責(zé),例如,創(chuàng)建一個新線程的時候,此時接下來是主線程繼續(xù)執(zhí)行,還是新線程執(zhí)行,這個事情是不能保證的,這就是“搶占式”執(zhí)行的重要特點(diǎn)。這時候就引入了線程等待:開發(fā)人員可以控制哪個線程先結(jié)束,哪個線程后結(jié)束。join()方法的執(zhí)行就會讓線程阻塞,一直阻塞到對應(yīng)線程執(zhí)行結(jié)束之后,才會繼續(xù)執(zhí)行。這就可以控制線程結(jié)束的先后順序。如果線程結(jié)束了才調(diào)用到j(luò)oin,此時也會立刻返回。
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é)束, 才會繼續(xù)執(zhí)行. t2.join(); System.out.println("主線程執(zhí)行完畢"); } }
執(zhí)行結(jié)果如下:
線程休眠
當(dāng)線程在正常運(yùn)行計算判斷邏輯此時就是在就緒隊列中排隊,調(diào)度器就會從就緒隊列中篩選出合適的PCB讓他上CPU執(zhí)行,如果某個線程調(diào)用sleep就會讓對應(yīng)的線程PCB進(jìn)入到阻塞隊列,線程一旦進(jìn)入到了阻塞隊列是沒有辦法上CPU執(zhí)行的,對于sleep進(jìn)入冷宮的時間是有限制的,時間到了之后,就自動被系統(tǒng)把這個PCB那回到原來的就緒隊列中了。
線程的狀態(tài)轉(zhuǎn)換
以上就是詳解Java創(chuàng)建線程的五種常見方式的詳細(xì)內(nèi)容,更多關(guān)于Java創(chuàng)建線程的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
springboot集成springsecurity 使用OAUTH2做權(quán)限管理的教程
這篇文章主要介紹了springboot集成springsecurity 使用OAUTH2做權(quán)限管理的教程,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-12-12logstash將mysql數(shù)據(jù)同步到elasticsearch方法詳解
這篇文章主要為大家介紹了logstash將mysql數(shù)據(jù)同步到elasticsearch方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12使用TraceId在Spring Cloud中實(shí)現(xiàn)線上問題快速定位
在微服務(wù)架構(gòu)中,服務(wù)間的互相調(diào)用使得問題定位變得復(fù)雜,在此背景下,TraceId為我們提供了一個在復(fù)雜環(huán)境中追蹤請求路徑和定位問題的工具,本文不僅介紹TraceId的基本概念,還將結(jié)合真實(shí)場景,為您展示如何在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