學習多線程的心得

来源:https://www.cnblogs.com/zjh996/archive/2023/03/03/17165444.html
-Advertisement-
Play Games

1.單線程 單線程:只有一個線程,即CPU只執行一個任務(一個線程) 1 class Hero{ 2 String name; 3 Hero(String name){ 4 this.name = name; 5 } 6 public void show(){ 7 System.out.printl ...


 

1.單線程

單線程:只有一個線程,即CPU只執行一個任務(一個線程)

 1 class Hero{
 2     String name;
 3     Hero(String name){
 4         this.name = name;
 5     }
 6     public void show(){
 7         System.out.println(name + "。。。。。");
 8     }
 9 }
10 
11 public class ThreadDemo {
12     public static void main(String[] args) {
13         Hero d1 = new Hero("亞瑟");
14         Hero d2 = new Hero("妲己");
15         Hero d3 = new Hero("貂蟬");
16         d1.show();
17         d2.show();
18         d3.show();
19     }
20 }

結果:,多線程順序可能會不一樣

2.多線程

多線程:就是多個線程同時運行,一個CPU執行多個任務(線程)

  • 優點:能讓代碼同時執行,可以大大提高效率
  • 能讓代碼同時執行,可以大大提高效率

1.主線程

java中,main方法是程式的入口,所以 main 方法被稱為:主方法

  • 執行 main 方法中代碼的線程,被稱為:主線程

需要註意的地方有 2 點:

  1. main 方法中的代碼都是有 main 線程執行的
  2. 在 main 方法中調用其他方法,那麼其他方法中的代碼也是由main線程執行

2.創建線程

除了主線程外,java允許我們自己創建線程,通常有 2 種方式:

  • 繼承Thread類
  • 實現Runnable介面

1.Thread類

java中有一個專門描述線程的類:Thread,通過繼承這個類可以創建自己的線程,比如:

 1 //1. 繼承Thread
 2 class Hero extends Thread{
 3     String name;
 4     Hero(String name){
 5         this.name = name;
 6     }
 7     //2. 覆寫run方法
 8     @Override
 9     public void run(){
10         System.out.println(name + "。。。。。");
11     }
12 }
13 
14 public class ThreadDemo {
15     public static void main(String[] args) {
16         //3. 創建線程對象
17         Hero yase = new Hero("亞瑟");
18         Hero daji = new Hero("妲己");
19         Hero diaochao = new Hero("貂蟬");
20         //4. 調用start方法:啟動線程,之後會自動執行 run 方法
21         yase.start();
22         daji.start();
23         diaochao.start();
24     }
25 }

結果:

註意:想要啟動一個線程,必須調用的是 start 方法,只是調用 run 方法並不會開啟新的線程

啟動線程方法start()和run()區別

在Java中啟動線程有兩種方式:調用start()方法和調用run()方法。它們的區別如下:

  • 調用start()方法:會啟動一個新線程,併在新線程中執行run()方法裡面的代碼。
  • 調用run()方法:不會啟動新線程,而是在當前線程中同步執行run()方法裡面的代碼。
  • 只有調用start()方法,才會表現出多線程的特性,不同線程的run()方法裡面的代碼交替執行。如果只是調用run()方法,那麼代碼還是同步執行的,必須等待一個線程的run()方法裡面的代碼全部執行完畢之後,另外一個線程才可以執行其run()方法裡面的代碼。

2.實現Runnable介面

實現 java 中的 Runnable 介面並覆寫 run 方法,也能創建線程,比如:

 1 //1. 實現Runnable介面
 2 class Hero implements Runnable{
 3     String name;
 4     Hero(String name){
 5         this.name = name;
 6     }
 7     //2. 實現run方法
 8     @Override
 9     public void run() {
10         System.out.println(name + "。。。。。");
11     }
12 }
13 public class ThreadDemo {
14     public static void main(String[] args) {
15         //3. 創建Thread對象時把Runnable對象作為參數扔進去
16         Thread t1 = new Thread(new Hero("亞瑟"));
17         Thread t2 = new Thread(new Hero("妲己"));
18         Thread t3 = new Thread(new Hero("貂蟬"));
19         //4.調用start方法,啟動一個線程,會自動調用run方法
20         t1.start();
21         t2.start();
22         t3.start();
23     }
24 }

結果:

 

通常在學習時,我們都用 ‘匿名內部類’創建線程,比如:

 1 public static void main(String[] args) {
 2     //使用 匿名內部類 創建線程
 3     Thread yase = new Thread(new Runnable() {
 4         @Override
 5         public void run() {
 6             System.out.println("使用匿名內部類創建一個線程。。。。。。");
 7         }
 8     },"yase");
 9     yase.start();
10 }

實現 Runnable 介面和繼承 Thread 比較:

  • java不支持多繼承,如果繼承Thread,那麼就不能繼承其他類了
  • java支持多實現,如果實現Runnable,不但可以實現其他介面,也可以繼承其他類

工作中推薦使用 Runnable 的方式創建線程

 

3.線程名

為了區分不同的線程,預設情況下每個線程都有名稱

1.獲取線程名

通過調用 getName() 方法獲取

 結果:

 

2.主線程名字

主線程也是有名稱的,但是我們無法使用 this.getName() 獲取主線程的名稱

  • 想要獲取主線程名稱,得先獲取主線程對象

Thread類中有一個靜態方法:currentThread(),用來獲取當前線程對象,比如:

 結果:

推薦使用:Thread.currentThread().getName() 獲取線程名稱

3.設置線程名

如果對預設的名稱不滿意,也可以在創建對象的時候自定義線程名稱

  1. 繼承 Thread 類

   代碼:

 1 class Hero extends Thread{
 2     String name;
 3     //1. 增加一個 threadName 參數,作為線程名
 4     Hero(String name,String threadName){
 5         //2. 調用super,把自定義名稱扔給父類
 6         super(threadName);
 7         this.name = name;
 8     }
 9     @Override
10     public void run(){
11         //3. 通過Thread.currentThread().getName()獲取線程名稱
12         System.out.println(name + "。。。。。"+Thread.currentThread().getName());
13     }
14 }
15 
16 public class ThreadDemo {
17     public static void main(String[] args) {
18         //4. 創建Thread實例對象,自定義線程名
19         Hero d1 = new Hero("亞瑟", "心靈戰士");
20         Hero d2 = new Hero("妲己","女僕咖啡");
21         Hero d3 = new Hero("貂蟬", "異域舞娘");
22         d1.start();
23         d2.start();
24         d3.start();
25     }
26 }

結果:

2.實現Runnable介面

 1 class Hero implements Runnable{
 2     String name;
 3     Hero(String name){
 4         this.name = name;
 5     }
 6     @Override
 7     public void run() {
 8         System.out.println(name + "。。。。。"+Thread.currentThread().getName());
 9     }
10 }
11 public class ThreadDemo {
12     public static void main(String[] args) {
13         //創建Thread對象時候,直接把線程名稱扔進去
14         Thread t1 = new Thread(new Hero("亞瑟"), "心靈戰士");
15         Thread t2 = new Thread(new Hero("妲己"), "女僕咖啡");
16         Thread t3 = new Thread(new Hero("貂蟬"), "異域舞娘");
17         t1.start();
18         t2.start();
19         t3.start();
20     }
21 }

結果:

4.線程名好處

線程名在實際工作中很重要,有以下好處

  1. 調試方便:當程式運行出現問題時,如果每個線程都有自己的名稱,可以快速定位問題
  2. 可讀性提高:如果代碼中存在多個線程,如果有名稱可以使得代碼更易於理解和維護。
  3. 日誌記錄方便:當出現問題時,如果線程有名稱,根據日誌可以更方便地識別每個線程的相關信息

例:

 1 class Hero implements Runnable{
 2     String name;
 3     Hero(String name){
 4         this.name = name;
 5     }
 6     @Override
 7     public void run() {
 8         //如果 name 為null,會報異常
 9         if(this.name.length()>0){
10             System.out.println("aaaaaaaaaaaa");
11         }
12         System.out.println(name + "。。。。。");
13     }
14 }
15 public class Demo {
16     public static void main(String[] args) {
17         Thread t1 = new Thread(new Hero(null));
18         Thread t2 = new Thread(new Hero("妲己"));
19         Thread t3 = new Thread(new Hero("貂蟬"));
20         //4.調用start方法,啟動一個線程,會自動調用run方法
21         t1.start();
22         t2.start();
23         t3.start();
24 
25     }
26 }

上面代碼中沒有設置線程名,結果:

上圖中雖然有預設的線程名,但是根據 Thread-0 很難定位到 Thread t1 = new Thread(new Hero(null)); 這句代碼在什麼地方

修改代碼,設置線程名:

1 Thread t1 = new Thread(new Hero("亞瑟"), "心靈戰士");
2 Thread t2 = new Thread(new Hero("妲己"), "女僕咖啡");
3 Thread t3 = new Thread(new Hero("貂蟬"), "異域舞娘");

結果:

4.多線程提高工作效率

示例:醫生給病人看病,一個人需要10分鐘,兩個人就需要20分鐘,如果兩個醫生,那麼一共需要10分鐘

示例:

 1 class SickPerson{
 2     String name;
 3 
 4     public SickPerson(String name){
 5         this.name = name;
 6     }
 7 }
 8 
 9 public class ThreadTest {
10     public static void main(String[] args) {
11         //1. 利用數組模擬兩個病人
12         SickPerson[] arr = new SickPerson[]{new SickPerson("yase"), new SickPerson("daji")};
13     
14         Thread bianque = new Thread(new Runnable() {
15             @Override
16             public void run() {
17                 System.out.println("扁鵲開始看病。。。。");
18                 //2. 使用 for 迴圈,一個個的處理病人
19                 for(int i = 0;i<arr.length;i++){
20                     System.out.println("處理" +arr[i].name+ ",需要5秒鐘。。。。。。");
21                     try {
22                         Thread.sleep(5000);//讓當前線程睡一會兒,然後接著執行
23                     } catch (InterruptedException e) {
24                     }
25                 }
26                 System.out.println("結束。。。。。。");
27             }
28         },"bianque");
29         bianque.start();
30     }
31 }

結果:

 

 開啟兩個線程

 1         //用數組模擬兩個病人
 2         SickPerson yase = new SickPerson("yase");
 3         SickPerson daji = new SickPerson("daji");
 4 
 5 //        SickPerson[] arr = new SickPerson[]{new SickPerson("yase"),new SickPerson("daji")};
 6         //創建內部類Runnable重寫run方法,使用for迴圈,處理病人,    線程睡眠,接著執行
 7         Thread bianque = new Thread(new Runnable() {
 8             @Override
 9             public void run() {
10                 System.out.println("扁鵲開始看病");
11                 System.out.println("給"+yase.name+"看病需要5秒");
12                     try {
13                         Thread.sleep(5000);
14                     } catch (InterruptedException e) {
15                     }
16             }
17         },"bianque");
18         bianque.start();
19         //開啟第二個線程
20         Thread huatuo = new Thread(new Runnable() {
21             @Override
22             public void run() {
23                 System.out.println("華佗開始看病");
24                 System.out.println("給"+daji.name+"看病要5秒");
25                     try {
26                         Thread.sleep(5000);
27                     } catch (InterruptedException e) {
28                         throw new RuntimeException(e);
29                     }
30             }
31         },"huatuo");
32         huatuo.start();

結果:

5.數據安全問題

1.前提

之前我們瞭解到,CPU是不斷的切換線程執行的,當CPU從A線程切換到B線程時,A線程就會暫停執行,比如:

1 public void run() {
2     //當線程執行到第一句代碼時,CPU很可能就切換其他線程執行,那麼當前線程就會暫停
3     System.out.println(name + "。。。。。");
4     System.out.println(name + "。。。。。");
5     System.out.println(name + "。。。。。");
6 }

2.多線程操作數據

當多個線程同時操作同一個數據時候,可能產生數據安全問題

示例:

 1 class Hero implements Runnable{
 2     //1. 定義一個靜態變數,多個線程同時操作它
 3     public static int num = 10;
 4     @Override
 5     public void run() {
 6         while (true){// while中用true,這是死迴圈,謹慎使用,這裡是為了演示效果
 7             //2. run方法中,對num--,當num<=0時,跳出迴圈
 8             if(num > 0){
 9                 //sleep(5),讓當前線程休眠5毫秒,此時CPU會執行其他線程
10                 try { Thread.sleep(5); } catch (InterruptedException e) {}
11                 num--;
12                 System.out.println(Thread.currentThread().getName() + "***********" + num);
13             }else{
14                 break;
15             }
16         }
17     }
18 }
19 public class ThreadDemo {
20     public static void main(String[] args) {
21         Hero hero = new Hero();
22         Thread yase = new Thread(hero, "yase");
23         Thread daji = new Thread(hero, "daji");
24         //3. 開啟兩個線程操作num
25         yase.start();
26         daji.start();
27     }
28 }

結果:

代碼中,當 num>0 時,才會執行輸出語句,但是卻輸出了負數

分析一下執行過程:

  • 2個線程剛開始正常執行,都會執行num--。。。。。
  • 當num=1時,假如 'yase' 先進入 if ,休眠5毫秒,這時 CPU 切換線程‘daji’進入 if
  • ‘yase’休眠結束,執行num--,接著‘daji’睡醒後也執行num--,最終num=-1
  • 註意:即使沒有 sleep 語句,也可能輸出負數,只不過概率太低

總結:一個線程在操作數據時,其他線程也參與運算,最終可能造成數據錯誤

解決保證操作數據的代碼在某一時間段內,只被一個線程執行,執行期間,其他線程不能參與,即使用synchronized關鍵字

6.線程同步

線程同步:就是讓線程一個接一個的排隊執行

  • 同步:即一步一步,也是一個接一個的意思

java中提供 synchronized 關鍵字用來實現同步,可以解決多線程時的數據安全問題

不過,使用 synchronized 的方式有很多種,我們一個一個解釋

1.synchronized代碼塊

格式:synchronized(鎖對象){

同步代碼塊

}

鎖對象:可以理解為鑰匙、通行證,只有線程拿到通行證後,才能執行 { } 中的代碼

  • 演示

使用 synchronized 修改 run 方法:

 1 public static Object obj = new Object();
 2 @Override
 3 public void run() {
 4     while (true){
 5         //使用同步代碼塊,obj被稱為鎖對象
 6         synchronized (obj){
 7             if(num > 0){
 8                 //sleep(5),讓當前線程休眠5毫秒,此時CPU會執行其他線程
 9                 try { Thread.sleep(5); } catch (InterruptedException e) {}
10                 num--;
11                 System.out.println(Thread.currentThread().getName() + "***********" + num);
12             }else{
13                 break;
14             }
15         }
16     }
17 }

結果:

原因:

  • 當線程執行到 synchronized (obj) 時,會獲取嘗試 obj 對象
  • synchronized 保證多線程下,只有一個線程能獲取到obj對象,其他線程就會阻塞(暫停)
    • 多個線程同時執行到 synchronized (obj) 這句代碼時,只有一個線程能夠拿到obj,其他線程暫停
  • 當持有 obj 的線程從 synchronized 退出後,會釋放 obj 對象,然後其他線程再次爭奪 obj

這樣就保證了 synchronized 中的代碼在某一時刻只能被一個線程執行

  • 好處和弊端

好處:解決多線程數據安全問題

弊端:同步代碼塊同時只能被一個線程執行,降低效率

synchronized註意事項

  • 必須是多個線程
    • 線程數>1,就是多線程
  • 多線程爭搶的鎖對象只能有一個,下圖中的做法要堅決抵制(會被開哦)

 2.同步方法

把 synchronized 放到方法上,那麼這個方法就是同步方法

  • 演示
 1 class Hero implements Runnable{
 2     public static int num = 10;
 3     public static Object obj = new Object();
 4     @Override
 5     public void run() {
 6         //run方法中調用同步方法
 7         this.show();
 8     }
 9     //同步方法
10     public synchronized void show(){
11         while (true){
12             if(num > 0){
13                 try { Thread.sleep(5); } catch (InterruptedException e) {}
14                 num--;
15                 System.out.println(Thread.currentThread().getName() + "***********" + num);
16             }else{
17                 break;
18             }
19         }
20     }
21 
22 }
23 public class ThreadDemo {
24     public static void main(String[] args) {
25         Hero hero = new Hero();
26         Thread yase = new Thread(hero, "yase");
27         Thread daji = new Thread(hero, "daji");
	   

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • for迴圈 迴圈的作用與分類 作用:讓代碼更加高效的重覆運行 分類:for迴圈和while迴圈 for迴圈結構 for 臨時變數 in 可迭代對象: 重覆執行的代碼1 重覆執行的代碼2 ... 可迭代對象 = 一個容器或者序列 # 遍歷字元串 for i in 'Python': print(i) ...
  • 現象 系統根據指定的日期範圍(LocalDateTime)查詢資料庫,結果與直接將SQL語句查詢不一致,系統查詢的並不是期望日期範圍的數據。 通過 LocalDateTime、LocaDate、LocalDate 作為時間插入數據時,時間不對 解決 更換 mysql 的驅動包版本在 8.0.22及以 ...
  • Spring提供的事務使用起來很方便,一個@Transactional註解就搞定全部,但是如果不註意,也會踩坑 提到事務就應該想到至少以下幾點: 1、在事務方法中加鎖,可能會導致鎖失效 無論是JVM自帶的鎖,還是分散式鎖,都有可能出現沒鎖住的情況 原因是解鎖先於事務提交,一旦鎖釋放後其它線程就可以獲 ...
  • 目錄 ElasticSearch 實現分詞全文檢索 - 概述 ElasticSearch 實現分詞全文檢索 - ES、Kibana、IK安裝 ElasticSearch 實現分詞全文檢索 - Restful基本操作 --待發佈 ElasticSearch 實現分詞全文檢索 - Java Spring ...
  • 摘要:本文將以Sermant的SpringBoot 註冊插件的性能測試及優化過程為例,分享在Java Agent場景如何進行更好的性能測試優化及在Java Agent下需要著重註意的性能陷阱。 作者:欒文飛 高級軟體工程師 一、背景介紹 Sermant是一個主打服務治理領域的Java Agent框架 ...
  • PHP語言線上運行編譯,是一款可線上編程編輯器,在編輯器上輸入PHP語言代碼,點擊運行,可線上編譯運行PHP語言,PHP語言代碼線上運行調試,PHP語言線上編譯,可快速線上測試您的PHP語言代碼,線上編譯PHP語言代碼發現是否存在錯誤,如果代碼測試通過,將會輸出編譯後的結果。 該線上工具由IT寶庫提 ...
  • Vue 3 備忘清單 Vue 3漸進式 JavaScript 框架 Vue 3 備忘清單的快速參考列表,包含常用 API 和示例入門,為開發人員分享快速參考備忘單。 開發速查表大綱 入門 介紹 創建應用 應用實例 通過 CDN 使用 Vue 使用 ES 模塊構建版本 模板語法 文本插值 原始 HTM ...
  • 前言 TCP三次握手和四次揮手是面試題的熱門考點,它們分別對應TCP的連接和釋放過程 1.TCP通信包含那幾步? TCP通信過程包括三個步驟:建立TCP連接通道,傳輸數據,斷開TCP連接通道 上圖主要包括三部分:*建立連接、傳輸數據、斷開連接。* 建立TCP連接很簡單,通過三次握手便可建立連接。 建 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...