探索Java多線程的核心概念與實踐技巧,帶你從入門到精通!

各位看官早安午安晚安呀

如果您覺得這篇文章對您有幫助的話

歡迎您一鍵三連,小編盡全力做到更好
歡迎您分享給更多人哦

今天我們來學習多線程編程-"掌握線程創建、管理與安全"

上一節課程我們鋪墊了一系列的東西,引出來了我們的多線程,接下來就讓小編帶領大家一起進入多線程的世界吧!!!

目錄

上一節課程我們鋪墊了一系列的東西,引出來了我們的多線程,接下來就讓小編帶領大家一起進入多線程的世界吧!!!

1.創建線程的方法:

1.1通過繼承Thread類,然后重寫run()方法

1.2.實現Runnnable接口,重寫run方法

1.3.二者區別

2.(通過jconsole觀察進程里面的多線程情況)

2.1.首先我們要知道每一個線程都是獨立的執行流

2.1.jconsole

3.Thread的一些其他方法

4.中斷一個線程(interrupt)

4.1.手動設置標志位

4.2.Thread內置的標志位

5.線程等待(join方法)

6.線程狀態

7.線程安全問題(最重要)

8.synchronized(原子性)


1.創建線程的方法:

線程實話說是操作系統的概念程序員想要操作線程肯定就需要操作系統提供給我們一些API,但是不同的操作系統提供的API又不同 (實話說這就讓小編想起了這個)

=>? java就針對上述的系統API進行了封裝(跨平臺嘛,(^-^)V 我們java太厲害啦)

=> 我們程序員只需要了解這一套API就夠啦~~

?java提供給我們的API就是Thread類,我們創建Thread對象就可以操作系統內部的線程啦

老規矩:學習一個類,先看他的構造方法

我們大概先學這幾種,還有一種線程分組的,小編現在也不了解,后續再給大家介紹吧~~

Runnable表示一個以運行的任務而已,這個任務是交給線程執行還是其他用圖我們不關心

1.1通過繼承Thread類,然后重寫run()方法

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我創建的一個新線程"); //  這個run方法里面描述了我這個線程要干啥}
}

這是我們自己創建的線程但是一個java程序跑起來,還有一個跟隨進程一起創建的線程,這個線程叫做主線程,這個主線程的入口方法是main方法。

這個run方法呢是我們創建的這個線程的入口方法!!!

一個程序跑起來,從哪個方法開始執行,哪個方法就是他的入口方法。

run方法是入口方法沒有錯,但是這個線程我們想要跑起來,肯定要啟動呀

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我創建的一個新線程");}
}
public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();  // 啟動線程,start方法才是真正的調用系統的API ,// 創建一個線程然后這個線程通過run方法跑起來System.out.println("這就是主線程");}

1.2.實現Runnnable接口,重寫run方法

class MyRunnable implements Runnable {@Overridepublic void run() {while (true) {System.out.println("我實現Runnable,重寫run方法實現的任務");}}
}public class Test {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();while (true) {System.out.println("主線程");}}}

當然我們通過匿名內部類lambda表達式實現也完全沒問題(上面我們繼承Thread類重寫run方法當然也可以使用匿名內部類的方式,但是lambda表達式不可以函數式接口定義(就是lambda表達式):一個接口有且只有一個抽象方法))

Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("通過匿名內部類實現");}});Thread t2 = new Thread(() -> {System.out.println("通過lambda表達式實現");});

1.3.二者區別

首先我們要明確創建線程的兩個關鍵操作

1.首先要明確要執行的任務(就是想要通過這個線程干啥)

2.調用系統API創建出線程。

好,現在我們就可以明確知道他倆的區別了,耦合性不同

1.第一種方法(繼承Thread的)把任務嵌套在了線程里面(后面想要修改任務,就要修改線程的源代碼(大工程))

2.第二種方法:我要讓線程執行其他任務,直接構造方法變成其他任務就好了。并且我這個任務又不是只給一個線程使用,其他線程想用的話直接傳過去就行了。

總之:把任務分離出來,耦合性更低了,效率更高!!!

2.(通過jconsole觀察進程里面的多線程情況

2.1.首先我們要知道每一個線程都是獨立的執行流

就拿這個代碼來說(但是這個代碼打印的太快了不好觀察,我們可以讓他休眠一下,慢一點打印

class MyThread extends Thread{@Overridepublic void run() {System.out.println("我創建的一個新線程");}
}public class Test {public static void main(String[] args){Thread t = new MyThread();t.start(); while (true) {System.out.println("主線程");}}

休眠的方法:sleep方法是Thread類的靜態方法(這個異常的處理很有講究的,大家盡量不要犯這樣的錯誤呀)

class MyThread extends Thread{@Overridepublic void run() {while(true){try {System.out.println("我創建的一個新線程");Thread.sleep(1000);//  sleep 這里就是一個類方法,我們直接調用就好了//  這里不能拋出異常,如果子類方法拋出額外的異常,調用者(可能只了解父類方法)可能不知道如何正確處理這些新的異常。//這里 父類的 run方法并沒有拋出異常} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class Test {public static void main(String[] args) throws InterruptedException {Thread t = new MyThread();t.start();while (true) {Thread.sleep(1000);System.out.println("主線程");}}

可以看到兩個線程的正在交替打印日志,并且不是一種規律進行打印。

你倆都是休眠1000ms,休眠后并且你你倆誰先執行都不一定(隨機的),這個取決于操作系統對于調度器的的具體實現。

通過并發執行,更加充分的的利用CPU資源

2.1.jconsole

jconsole(觀察多線程屬性的一種方式,還有IDEA的一種一個)

jdk里面的一個可執行程序,jdk的位置:

如何啟動:

1.啟動之前保證idea的程序已經跑起來了

2.有的兄弟需要管理員方式運行(正常情況不行的話)

=>

3.Thread的一些其他方法

    public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println("創建的線程");},"主線程");System.out.println(t.getId());  //java 給這個線程分配的idSystem.out.println(t.getName()); // 我設置的線程名字  -> 主線程System.out.println(t.isDaemon()); // 后臺線程? falseSystem.out.println(t.isAlive());// 線程是否存活  false 還沒開始運行t.setDaemon(true);  // 設置t線程為后臺線程//這四行一下就執行完了t.start();System.out.println(t.isAlive());  //true ,不過就存活這一下就結束了,其他線程都結束了//后臺線程肯定結束了// 最后發現沒打印創建的線程}

4.中斷一個線程(interrupt)

java里面就是讓一個線程快點結束(而不是直接讓一個線程中間斷掉(這樣一個線程會導致殘留數據,不太科學))

4.1.手動設置標志位

我們用一個isQuit作為標志位,讓主線程5秒后修改isQuit的值

 public static  boolean isQuit = false;  // 類屬性public static void main(String[] args) throws InterruptedException {//這里就涉及到了變量的捕獲的語法,但是Thread t = new Thread(() ->{while(!isQuit){//我這里捕獲到的其實是拷貝外部變量的一份System.out.println("我新創建的一個線程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}System.out.println("線程工作完畢");},"t線程");t.start();Thread.sleep(5000); // 五秒之后設置isQuit = trueisQuit = true;}

給大家提一個問題,畢竟當時小編在寫這個代碼的時候也忘了

這里不涉及是涉及匿名內部類的變量捕獲嗎?為什么這里不報錯呢?

答:這個時候isQuit是成員變量,此時lambda訪問這個成員,就不再是變量捕獲的語法了,變成了“內部類訪問外部類”的語法,就沒有final(實際上沒有被修改過的也算)的限制了

另外:對于基本數據類型,捕獲的是它們的值的副本(實際上就是復制了一份)

如果是局部變量的話就會報錯,因為修改了

如果一個局部變量不能解決問題就可以考慮把這個局部變量換成成員變量

但是這樣寫還是不太舒服

1.還要我們自己創建

2.并且如果另一個線程正在sleep就不能夠及時響應

java有沒有已經設置好的標志位呢?當然!!!

4.2.Thread內置的標志位

t.interrupt的作用:

    public static void main(String[] args) {Thread t = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {System.out.println("我新建的一個線程");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();   //  這里扔出一個異常,也僅僅只是打印出異常的位置信息而已,也沒真正的處理什么}}System.out.println("我的循環即將終止");});t.start();try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("讓線程終止");t.interrupt();// 把線程內部的標志位設置為true}

我們發現線程確實還是在運行,一張圖搞懂

還有一個靜態方法,那大家都用的是一個標志位實在是不太科學(但是如果是你想讓兩個東西抵消好像還可以)(先記住吧)

5.線程等待(join方法)

線程等待:一個線程等待另一個線程結束再執行(控制線程結束的順序)

    public static void main(String[] args) throws InterruptedException {  //線程阻塞拋出的異常Thread t =new Thread(() ->{while(true){System.out.println("我新建的一個線程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("等待線程開始");t.join(5000);  // 讓主線程等待5s,也會造成線程阻塞,也要拋出異常System.out.println("join 等待結束");}

   public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {System.out.println("我新建的一個線程");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}});long t1 = System.currentTimeMillis();t.start();t.join();  // 也會造成線程阻塞,也要拋出異常long t2 = System.currentTimeMillis(); // 調度,還有創建線程的開銷System.out.println(t2 - t1);  //}

6.線程狀態

    public static void main(String[] args) {for(Thread.State state : Thread.State.values()){System.out.println(state);}}

//觀察狀態public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {try {Thread.sleep(1000);System.out.println("11111111");} catch (InterruptedException e) {throw new RuntimeException(e);}});System.out.println(t.getState()); //  NEWt.start();System.out.println(t.getState()); //RUNNABLEThread.sleep(500);System.out.println(t.getState()); //TIMED_WAITING: 由于sleep這種固定時間的方式產生的阻塞System.out.println(t.getState());  // terminated,Thread對象還在,但是我創建的對象已經跑完了}

7.線程安全問題(最重要)

簡述:

單個線程下執行可以,但是多個線程執行就出現bug,這種就是線程安全問題

(寫出的代碼和預想的結果不同就是bug!!!)

我們先看一個代碼:兩個線程同時修改一個靜態成員變量

   public static int count;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

竟然不是10000

但是我們修改一下代碼:結果就是10000了

   public static int count;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){count++;}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {count++;}});t1.start();t1.join();t2.start();t2.join();System.out.println(count);}

到底是什么原因呢?

count++這個操作其實是分成三步進行的(CPU通過三個指令來實現的)

這種也可能(所以說這排列組合是無數種)

歸根到底:

這是因為線程的搶占式運行,兩個線程不是往上累加而是獨立運行。

如果這個count++變成一條指令(原子的),那么線程隨便執行都沒問題了

所有的都變成了樣子,這個時候我們就想到給這個count++進行加鎖,把他變成一條指令!!!

8.synchronized(原子性)

我們就需要使用synchronized關鍵字給一個代碼塊進行加鎖

   public static int count;public static void main(String[] args) throws InterruptedException {Object locker = new Object();Thread t1 = new Thread(() -> {for(int i = 0; i < 5000; i++){synchronized (locker){count++;}//synchronized,// 我們把要加鎖的代碼放在這個代碼塊里面就行了//我們如果對兩個線程加相同的鎖,就會造成"鎖競爭/鎖沖突“,畢竟我們就只有這一把鎖,線程2必須等線程1解鎖然后再加鎖//    由于我們是對一個變量進行操作,我們還是盡量加同一把鎖,兩把鎖,還是會并發執行//導致:那么一個線程對 count 的修改可能不會被另一個線程立即看到.}});Thread t2 = new Thread(() ->{for (int i = 0; i < 5000; i++) {synchronized (locker){count++;}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}

之后就變成這么執行了,變成了串行執行

t2線程由于鎖的競爭(拿不到locker的監視器鎖)就只能阻塞等待,一直等待到t1線程unlock之后才能獲得這把鎖,這樣就避免了load,add,save的穿插執行。

如果我們兩個線程分別給兩個線程進行加鎖,就還是會穿插執行。因為沒有了鎖競爭!!!

后面的線程安全問題后面再講吧~~~(上一次看鎧甲勇士還是在上一次~~~哈哈哈)

上述就是進程的"黑匣子":PCB如何記錄著任務的一生

的全部內容了,線程的出現,我們的效率又得到了很大的提升~~~,但是管理和安全也是一個很大的問題。預知后事如何,請聽下回分解~~~

能看到這里相信您一定對小編的文章有了一定的認可。

有什么問題歡迎各位大佬指出
歡迎各位大佬評論區留言修正~~

您的支持就是我最大的動力???!!!

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/71804.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/71804.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/71804.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

互動多媒體項目 自行車互動

該項目為UE4 +自行車騎行速度 互動項目 結果預覽 : 1. 獲取自行車速度 這里使用的是Arduino單片機 + 霍爾傳感器 霍爾傳感器: 單片機完整代碼: #define HALL_PIN 2 // 霍爾傳感器連接到D2(中斷引腳) volatile unsigned long lastTime = 0; // …

STM32——GPIO介紹

GPIO(General-Purpose IO ports,通用輸入/輸出接口)模塊是STM32的外設接口的核心部分,用于感知外界信號(輸入模式)和控制外部設備(輸出模式),支持多種工作模式和配置選項。 1、GPIO 基本結構 STM32F407 的每個 GPIO 引腳均可獨立配置,主要特性包括: 9 組 GPIO 端口…

學習筆記:Python網絡編程初探之基本概念(一)

一、網絡目的 讓你設備上的數據和其他設備上進行共享&#xff0c;使用網絡能夠把多方鏈接在一起&#xff0c;然后可以進行數據傳遞。 網絡編程就是&#xff0c;讓在不同的電腦上的軟件能夠進行數據傳遞&#xff0c;即進程之間的通信。 二、IP地址的作用 用來標記唯一一臺電腦…

DeepSeek 醫療大模型微調實戰討論版(第一部分)

DeepSeek醫療大模型微調實戰指南第一部分 DeepSeek 作為一款具有獨特優勢的大模型,在醫療領域展現出了巨大的應用潛力。它采用了先進的混合專家架構(MoE),能夠根據輸入數據的特性選擇性激活部分專家,避免了不必要的計算,極大地提高了計算效率和模型精度 。這種架構使得 …

JetBrains學生申請

目錄 JetBrains學生免費授權申請 IDEA安裝與使用 第一個JAVA代碼 1.利用txt文件和cmd命令運行 2.使用IDEA新建項目 JetBrains學生免費授權申請 本教程采用學生校園郵箱申請&#xff0c;所以要先去自己的學校申請校園郵箱。 進入JetBrains官網 點擊立即申請&#xff0c;然…

LINUX網絡基礎 [五] - HTTP協議

目錄 HTTP協議 預備知識 認識 URL 認識 urlencode 和 urldecode HTTP協議格式 HTTP請求協議格式 HTTP響應協議格式 HTTP的方法 HTTP的狀態碼 ?編輯HTTP常見Header HTTP實現代碼 HttpServer.hpp HttpServer.cpp Socket.hpp log.hpp Makefile Web根目錄 H…

六十天前端強化訓練之第八天到第十四天——綜合案例:用戶管理系統

歡迎來到編程星辰海的博客講解 看完可以給一個免費的三連嗎&#xff0c;謝謝大佬&#xff01; 目錄 一、知識體系詳解 1. 變量與作用域 2. 箭頭函數特性 3. 數組高階函數 4. DOM操作原理 5. 事件傳播機制 6. 閉包核心原理 7. 原型繼承體系 8. Promise工作流程 二、綜…

技術周總結 03.03 - 03.09 周日(Java監控 SpringAI)

文章目錄 一、03.05 周三二、03.08 周六openAI 的Spring開發 一、03.05 周三 jvisualvm java自帶的監控和故障排除工具 命令行執行后&#xff0c;會出現 JConsole 二、03.08 周六 openAI 的Spring開發 引入 spring-ai-openai-spirng-boot-starter 依賴 Spring AI http…

DeepSeek:中國AGI破局者的技術革命與生態重構

在AI領域被"算力霸權"與"技術壟斷"籠罩的今天&#xff0c;一家來自杭州的初創公司正以顛覆性創新撕開行業鐵幕。DeepSeek&#xff08;深度求索&#xff09;不僅重新定義了AGI技術研發范式&#xff0c;更通過開源生態構建引發全球AI產業格局的深度重構。 一…

manus本地部署使用體驗

manus部署 https://github.com/mannaandpoem/OpenManus git clone https://github.com/mannaandpoem/OpenManus.git 或者手工下載zip包解壓&#xff0c;包很小&#xff0c;只有幾百K。 cd OpenManus-main #創建python環境&#xff0c;有python3的可以用python3 python -m ven…

【統計至簡】【入門測試1】給定數據矩陣X,如何求其質心、中心化數據、標準化數據、格拉姆矩陣、協方差矩陣、相關系數矩陣

給定數據矩陣X&#xff0c;如何求其質心、中心化數據、標準化數據、格拉姆矩陣、協方差矩陣、相關系數矩陣。 ??設數據矩陣 X X X是一個 n p n\times p np的矩陣&#xff0c;其中 n n n是樣本數量&#xff0c; p p p是變量數量&#xff0c; X ( x i j ) X (x_{ij}) X(xij?…

CI/CD—Jenkins、Maven安裝

Jenkins簡介 Jenkins 是一款廣泛使用的開源持續集成和持續交付&#xff08;CI/CD&#xff09;工具&#xff0c;以下是對它的詳細介紹&#xff1a; 基本信息 起源與發展&#xff1a;Jenkins 最早起源于 Hudson 項目&#xff0c;后來從 Hudson 項目中分離出來獨立發展。自 2011 …

抽獎系統測試報告

項目鏈接: 管理員登錄頁面 項目功能: 管理員登錄: 登錄方式分為兩種: 手機號密碼登錄: 正確輸入密碼和手機號登錄 短信驗證碼登錄: 輸入手機號,等待驗證碼,輸入驗證碼登錄 管理員注冊: 登錄頁面點擊注冊按鈕即可注冊管理員身份 人員管理模塊: 人員管理模塊分為注冊…

【高級篇】大疆Pocket 3加ENC編碼器實現無線RTMP轉HDMI進導播臺

【高級篇】大疆Pocket 3加ENC編碼器實現無線RTMP轉HDMI進導播臺 文章目錄 準備工作連接設備RTMP概念ENCSHV2推流地址設置大疆Pocket 3直播設置總結 老鐵們好&#xff01; 很久沒寫軟文了&#xff0c;今天給大家帶了一個干貨&#xff0c;如上圖&#xff0c;大疆Pocket 3加ENC編…

【 <一> 煉丹初探:JavaWeb 的起源與基礎】之 Servlet 與 JSP 的協作:MVC 模式的雛形

<前文回顧> 點擊此處查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、Servl…

【不是廣告】華為昇騰的一小步,Pytorch的一大步

華為昇騰的一小步&#xff0c;Pytorch的一大步 關鍵詞 首個、中國首個、全球第十、最高級別&#xff01;看看這些字眼&#xff0c;就知道事情不簡單&#xff01; 書接上文《Pytorch的一小步&#xff0c;昇騰芯片的一大步》 在2023年10月4日PyTorch 2.1版本的發布博客上&…

python從入門到精通(二十六):python文件操作之Word全攻略(基于python-docx)

python文件操作之word技巧大全 word技巧基礎到高級操作大全A.準備工作1. 安裝python-docx庫2. 導入庫 B.基礎操作1. 創建Word文檔1.1 創建文檔對象1.2 添加word標題1.3 添加word段落1.4 設置段落樣式1.5 創建有序列表1.6 創建無序列表1.7添加word分頁1.8 添加word圖片1.9 添加w…

Debian二次開發一體化工作站:提升科研效率的智能工具

在科研領域&#xff0c;數據處理是實驗成功的關鍵環節之一。隨著實驗數據的復雜性和規模不斷增加&#xff0c;傳統的數據處理方法已經難以滿足科研人員的需求。這時&#xff0c;一體化工作站應運而生&#xff0c;成為科研實驗數據處理的 “智能大腦”。 一體化工作站&#xff…

linux學習(五)(服務器審查,正常運行時間負載,身份驗證日志,正在運行的服務,評估可用內存)

服務器審查 在 Linux 中審查服務器的過程包括評估服務器的性能、安全性和配置&#xff0c;以確定需要改進的領域或任何潛在問題。審查的范圍可以包括檢查安全增強功能、檢查日志文件、審查用戶帳戶、分析服務器的網絡配置以及檢查其軟件版本。 Linux 以其穩定性和安全性而聞名…

Redis- 大key

大key 什么是大key問題大key的危害大key的識別方法大key問題的解決方案數據結構優化與拆分壓縮與序列化優化預防與監控機制 什么是大key問題 大Key問題是指在Redis等內存數據庫中&#xff0c;某個Key對應的value數據結構過大&#xff0c;通常是指單個Key的大小超過10KB甚至達到…