java多線程(常用方法、實現方式、線程安全問題、生命周期、線程池)

多線程相關的三組概念

程序和進程
  1. 程序(program):一個固定的運行邏輯和數據的集合,是一個靜態的狀態,一般存儲在硬盤中。簡單來說就是我們編寫的代碼

  2. 進程(process):一個正在運行的程序,一個程序的一次運行,是一個動態的概念般存儲在內存中。 例如:command + option + esc,打開任務管理器可以看到所有進程

  3. 進程是正在運行的一個程序:

    程序是靜態的:QQ這個程序,如果不運行就是存在磁盤中,不占內存,所以是靜態的。

    進程是動態的:QQ這個程序,如果在運行就運行在內存中,占用內存,所以是動態的。

進程和線程
  1. 進程(process):一個正在運行的程序,一個程序的一次運行,是一個動態的概念存儲在內存中。

    • 比如我們使用QQ,就啟動了一個進程,操作系統就會為該進程分配內存空間,比如我們再啟動迅雷,就會又啟動一個進程,操作系統會給迅雷分配新的內存空間

    • 進程是程序的一次執行過程,或是正在運行的一個程序,是一個動態的過程,有它自身的產生,存在和消亡的過程

  2. 線程(thread):一條獨立的執行路徑。多線程,在執行某個程序的時候,該程序可以有多個子任務,每個線程都可以獨立的完成其中一個任務。在各個子任務之間,沒有什么依賴關系,可以單獨執行。

    • 線程是由進程創建的,是進程的一個實體

    • 一個進程可以擁有多個線程

  3. 進程和線程的關系: 進程是用于分配資源的單位 一個進程中,可以有多條線程;但是一個進程中,至少有一條線程

    比如當我們啟動迅雷時,就開啟一個進程,使用迅雷時我們可以同時下載5個電影資源,每一個下載任務就是一個線程

    線程不會獨立的分配資源,一個進程中的所有線程,共享同一個進程中的資源

并行和并發
  1. 并行(parallel):多個任務(進程、線程)同時運行。在某個確定的時刻,有多個任務在執行,多個cpu可以實現并行。 條件:有多個cpu,多核編程

  2. 并發(concurrent):多個任務(進程、線程)同時發起。不能同時執行的(只有一個cpu),只能是同時要求執行。就只能在某個時間片內,將多個任務都有過執行。一個cpu在不同的任務之間,來回切換,只不過每個任務耗費的時間比較短,cpu的切換速度比較快,所以可以讓用戶感覺就像多個任務在同時執行。并發就是同一時刻,多個任務交替執行,造成一種,“貌似同時”的錯覺,簡單來說,單核cpu實現的多任務就是并發

  3. 舉例:

    1. 并發

    image-20230102225106897

    image-20230102225242835

    2.并行

    image-20230102225635068

一個人可以同時做多件事兒,就是并發

多個人可以同時做多件事兒,就是并行

并發是多個任務之間互相搶占資源

并行是多個任務之間不互相搶占資源

并發的關鍵是你有處理多個任務的能力,不一定要同時。 并行的關鍵是你有同時處理多個任務的能力。

最關鍵的點就是:是否是『同時』。

但是我們的電腦也會存在并發與并行同時存在的情況

并行和并發的圖示
并發的本質:不同任務來回切換:

image-20230102180832171

并行和并發的比較:

image-20230102181327022

image-20230102181400200

image-20230102181418596

問&答:

問:既然只有一個cpu,還需要在不同的任務之間來回切換,那么效率到底是提升了還是降低了?

答:

1、提升了:整個系統的效率提升了(cpu的使用率提升了),對于某個具體的任務而言,效率是降低了

2、并發技術,解決的是不同的設備之間速率不同的問題 cpu: 10^-9秒 大概是千分之一毫秒 執行一次. 0.000000001 內存:10^-6秒 0.000001 磁盤:10^-3秒 0.001 人:10^0秒.

3、cpu 的效率非常高,給磁盤一個指令,讓磁盤尋找某個字節,cpu就趕緊切換到其它的任務,執行其它的

計算、存儲的操作。這樣子就可以讓計算機系統中的所有設備都運轉起來,提升了cpu的使用率。絕不能出現

cpu這種昂貴的設備等待磁盤這種廉價設備的情況。

多線程的實現方式

多線程實現方式一:繼承方式
  1. 繼承Thread

  2. 步驟:

    1、定義一個類,繼承Thread類

    2、重寫自定義類中的run方法,用于定義新線程要運行的內容

3、創建自定義類型的對象

4、調用線程啟動的方法:start()方法

代碼示例:
/*** 多線程實現方式一:實現方式*/
public class Simple01 {public static void main(String[] args) {// 創建自定義類型的對象MyThread mt = new MyThread();// 調用對象的start方法,啟動線程mt.start(); // 用主線程 調用自定義的線程
?// 讓主線程(主方法本來就所在的線程)繼續運行for (int i = 1; i <= 100; i++) {System.out.println(i);}// 現象是主線程內容和新線程內容交替執行,兩條線程相互沒有依賴關系}
?
}
?
// 定義一個類,繼承Thread類
class MyThread extends Thread {//重寫run方法,定義線程的運行內容@Overridepublic void run() {for (int i = 0; i <=100; i++) {System.err.println(i);}}
}
多線程實現方式二:實現方式
  1. 實現Runnable接口:

    Runnable接口的實現類對象,表示一個具體的任務,將來創建一個線程對象之后,讓線程執行這個任務

  2. 步驟: 1、定義一個任務類,實現Runnable接口 2、重寫任務類中的run方法,用于定義任務的內容 3、創建任務類對象,表示任務 4、創建一個Thread類型的對象,用于執行任務類對象 5、調用線程對象的start方法,開啟新線程

代碼示例:
/*** 多線程實現方式二:實現方式*/
public class Simple02 {public static void main(String[] args) {// 創建任務類對象MyTask myTask = new MyTask();// 創建線程對象,綁定要執行的任務Thread thread = new Thread(myTask);// 啟動新線程thread.start();
?for (int i = 1; i <= 10000; i++) {System.out.println(i);}}
}
// 定義一個任務類
class MyTask ?implements ?Runnable{@Override// 重寫run方法,定義任務的內容public void run() {for (int i = 1; i <= 10000; i++) {System.err.println(i);}}
}
兩種方式的比較
  1. 代碼復雜程度:

    繼承Thread方式簡單

    實現Runnable接口的方式比較復雜

  2. 設計:java中只支持單繼承、不支持多繼承

    • 繼承方式:

      某個類繼承了Thread類,那么就無法繼承其他業務中需要的類型,就限制了我們的設計。所以擴展性較差。

    • 實現方式:

      某個類通過實現Runnable的方式完成了多線程的設計,仍然可以繼承當前業務中的其它類型,擴展性較強。

  3. 靈活性:

    • 繼承方式:

      將線程對象和任務內容綁定在了一起,耦合性較強、靈活性較差

    • 實現方式:

      將線程對象和任務對象分離,耦合性就降低,靈活性增強:同一個任務可以被多個線程對象執行,某個線程對象也可以執行其它的任務對象。并且將來還可以將任務類對象,提交到線程池中運行;任務類對象可以被不同線程運行,方便進行線程之間的數據交互。

使用匿名內部類創建線程對象
代碼示例:
/*** 使用匿名內部類創建線程對象*/
public class Simple03 {public static void main(String[] args) {
?//第一條線程打印1-100,使用繼承的方式new Thread() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...extends");}}}.start();
?//第二條線程打印1-100,使用實現的方式new Thread(new Runnable() {@Overridepublic void run() {for(int i = 1; i <= 100; i++) {System.out.println(i + "...implements" );}}}).start();}
}

Thread類中的常用方法

獲取線程名稱
  1. getName():獲取線程的名稱

  2. 注意事項: 1、如果沒有給線程命名,那么線程的默認名字就是Thread-x,x是序號,從0開始

代碼示例:
/*** 獲取線程名稱*/
public class Simple04 {public static void main(String[] args) {Thread thread = new Thread() {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.setName("線程0");thread.start();Thread thread1 = new Thread() {@Overridepublic void run() {System.out.println("name1:"+this.getName() );}};thread1.setName("線程1");thread1.start();}
}
設置線程名稱
  1. setName(String name) 使用線程對象的引用,調用該方法給線程起名字

  2. 構造方法: Thread(String name):給線程通過構造方法起名字 Thread(Runnable target, String name):在給線程target對象賦值的同時,也給線程起名

  3. 注意事項: 線程設置名字,既可以在啟動之前設置,也可以在啟動之后設置

代碼名稱:
/*** 設置 線程名稱*/
public class Simple05 {public static void main(String[] args) {Thread thread = new Thread("線程0") {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
?new Thread(new Task(),"線程1") {@Overridepublic void run() {System.out.println("name:"+this.getName() );}}.start();}
}
class Task implements Runnable{@Overridepublic void run() {}
}
獲取當前線程對象

1、作用: 某段代碼只要是在執行,就一定是在某個方法中執行的,只要在某個方法中,代碼就一定是被某個線程執行的。所以任意一句代碼,都有執行這句代碼的線程對象。 可以在任意位置,獲取當前正在執行這段代碼的線程對象 2、方法: Thread Thread.currentThread(); 返回當前正在執行這段代碼的線程對象的引用 哪條線程在執行這段代碼,返回的就是哪條線程

代碼示例:
/*** 獲取當前線程對象*/
public class Simple06 {public static void main(String[] args) {Thread thread = new Thread("線程0") {@Override// 繼承方式 可以加this關鍵字找到getNamepublic void run() {System.out.println("name:"+this.getName() );}};thread.start();
// ? ? ?  System.out.println(thread.getName());
?new Thread(new Task(),"線程1").start();}
}
class Task implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread());}
}
線程休眠
  1. Thread.sleep(long time) 讓當前線程休眠 time:休眠的時間,單位是毫秒

  2. 作用: 當某段代碼在某個位置需要休息的時候,就使用線程的休眠 無論哪個線程在運行這段代碼,碰到sleep方法都需要停下休息

  3. 注意事項: 有一個異常,中斷異常:InterruptedException 在普通方法中,可以聲明拋出 在重寫的run方法中,必須只能處理,不能聲明

    代碼示例:
    /*** 線程休眠*/
    public class Simple07 {public static void main(String[] args) throws InterruptedException {
    // ? ? ?  InterruptedException 中斷異常
    // ? ? ?  System.out.println(1);
    // ? ? ?  Thread.sleep(1000);
    // ? ? ?  System.out.println(2);
    ?MyCountDown myCountDown = new MyCountDown();Thread thread = new Thread(myCountDown);thread.start();}
    }
    class MyCountDown implements Runnable {@Overridepublic void run() {// 在run方法中,異常只能處理,不能拋出for (int i = 5; i >= 1; i--) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(i);}}
    }
守護線程
  1. setDaemon(boolean flag):每條線程默認不是守護線程,只有設定了flag為true之后,該線程才變成守護線程

  2. isDaemon():判斷某條線程是否是守護線程

  3. 守護線程的特點: 守護線程就是用于守護其它線程可以正常運行的線程,在為其它的核心線程準備良好的運行環境。

    如果非守護線程全部死亡,守護線程就沒有存在的意義了,一段時間之后,虛擬機也一同結束。

    虛擬機結束了,所有線程都結束了。

  4. 別名: 后臺線程

    好比前臺的表演都是需要后臺工作人員的準備支持,前臺沒有人表演了,那么后臺工作人員就沒有存在的意義了。

    在jvm中,gc就是守護線程,作用就是當用戶自定義的線程以及主線程執行完畢后,gc才停止工作,

    不然的話,當主線程沒有執行結束,gc就結束了,這樣就會造成gc垃圾還沒有清理完,主線程早就掛了。

    (注意:主線程不是守護線程,gc才是守護線程)。

    我們只需要了解什么是守護線程的概念,即可。

代碼示例:
/*** 線程守護*/
public class Simple08 {public static void main(String[] args) {
?Thread thread = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println(i+"...守護線程");}});// 設置為守護線程thread.setDaemon(true);System.out.println(thread.isDaemon());// 啟動之后,系統中就有兩條線程:主線程和守護線程thread.start();
?for (int i = 1; i <= 50; i++) {System.out.println(i + "...main");}//主線程運行完成之后,系統中就只剩下了守護線程//如果非守護線程全部死亡,守護線程就沒有存在的意義了,//一段時間后,虛擬機也一同結束。//當主線程執行結束,jvm還沒有立即結束,所以守護線程還會執行一段時間}
}

在實際開發中,守護線程(Daemon Thread)有以下主要作用:

  1. 后臺任務執行:守護線程通常用于執行后臺任務,這些任務不需要對用戶可見,且不影響主線程的執行。比如,在 Web 服務器中,可以使用守護線程處理一些定時任務、日志記錄等后臺操作,而不會影響用戶請求的處理。

  2. 資源回收:守護線程在 JVM 關閉時會自動終止,并且它們的執行不會阻止 JVM 退出。因此,守護線程通常用于執行資源回收工作,如垃圾回收器(Garbage Collector)線程就是守護線程,負責回收不再使用的對象。

  3. 定時任務:守護線程可以用于執行定時任務,如定時更新緩存、定時發送心跳包等。這樣可以在后臺自動執行這些任務,而不需要手動觸發。

  4. 后臺服務:某些后臺服務可能需要一直運行,并隨著應用的啟動而啟動。這些服務可以作為守護線程運行,以確保在應用退出時能夠自動終止。

多線程中的線程安全問題

問題描述

某段代碼在沒有執行完成的時候,cpu就可能被其它線程搶走,結果導致當前代碼中的一些數據發生錯誤

原因:沒有保證某段代碼的執行的完整性、原子性

希望:這段代碼要么全都執行,要么全都沒有執行

代碼示例:
/*** 多線程中的線程安全問題* 模擬出票系統*/
public class Simple01 {public static void main(String[] args) {Ticket ticket = new Ticket(); // 堆中只有一個對象Thread thread1 = new Thread(ticket, "窗口1");Thread thread2 = new Thread(ticket, "窗口2");Thread thread3 = new Thread(ticket, "窗口3");thread1.start();thread2.start();thread3.start();}
}
?
class Ticket implements Runnable {
?private int ticktNum = 0;//票號
?// 線程售票public void run() {while (ticktNum < 10) { // 模擬售10張票// 1.模擬出票時間try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}// 2.打印進程號和票號String name = Thread.currentThread().getName();ticktNum = ++ticktNum;System.out.println("線程" + name + "售票:票號" + ticktNum);}// 出現的問題,某票號有可能被賣了多次// 不存在的票10號也有可能出現出現了// 出現不存在的票號原因:線程1,2在tickNum++ 時候被其他線程搶走了。倒置被累加多次
?}
}

多個線程對全局變量只讀,不寫,那么一般這個變量是安全的,

多個線程同時執行寫的操作,那么就需要考慮線程同步的問題,否則會影響線程安全問題

綜上所述,線程安全問題根本原因:

  1. 多個線程在操作共享的數據;

  2. 多個線程對共享數據有寫的操作;

問題解決:

要解決以上線程問題,只要在某個線程修改共享資源的時候,其它線程不能修改該資源,等待修改完畢同步之后,才能去搶奪CPU資源,完成對應的操作,保證了數據的同步性,解決了線程不安全的現象。

為了保證每個線程都能正常執行共享資源操作,Java引入了線程同步機制:

構造代碼塊

1) 同步代碼塊(synchronized)

2) 同步方法 (synchronized)

同步代碼塊
  1. 同步代碼塊: 使用一種格式,達到讓某段代碼執行的時候,cpu不要切換到影響當前代碼的代碼上去 這種格式,可以確保cpu在執行A線程的時候,不會切換到其它線程上去

  2. 使用格式 synchronized (鎖對象) { 需要保證完整性、原子性的一段代碼(需要同步的代碼) }

  3. 使用同步代碼塊之后的效果: 當cpu想去執行同步代碼塊的時候,需要先獲取到鎖對象,獲取之后就可以運行代碼塊中的內容;

    當cpu正在執行當前代碼塊內容時,cpu可以切換到其它代碼,但是不能切換到具有相同鎖對象上;

    當cpu執行完當前代碼塊中代碼之后, 會釋放鎖對象,cpu就可以運行其它具有當前鎖對象的同步代碼塊了;

代碼示例:
/*** 同步代碼塊*/
public class Simple02 {public static void main(String[] args) {Printer p = new Printer();
?Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {Object object = new Object();public void print1() {// 線程1在獲取了obj鎖之后,線程2就無法獲取obj鎖,// 就只能等待線程1運行完這個方法,兩條線程才能再次共同爭奪鎖對象synchronized (object) {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}
?
?}
?public void print2() {synchronized (object) {System.err.print("線");System.err.print("程");System.err.println("鎖");}
?
?}
}
// 不加鎖的話,會出現:碼上未來碼 ? 線程鎖碼 等數據不準確現象
// A線程執行print1完畢 釋放obj對象,然后cpu被B線程搶去執行print2方法,
// B線程執行print2完畢,釋放obj對象,然后cpu又被A或者B 線程搶去執行對應方法,
同步方法

1、同步代碼塊:在某段代碼執行的時候,不希望cpu切換到其他影響當前線程的線程上去,就在這段代碼上加上同步代碼塊,至于該方法中一些只讀的成員變量就不需要放在同步代碼塊中了。

所以同步代碼塊中,只存放需要保證完整性、原子性的一段代碼。

2、如果某個方法中,所有的代碼都需要加上同步代碼塊,使用同步方法這種【簡寫格式】來替代同步代碼塊

3、同步方法的格式:

權限修飾符 [靜態修飾符] synchronized 返回值類型 方法名稱(參數列表) {

需要同步的方法體

}

4、同步方法的鎖對象

如果是非靜態的方法,同步方法的鎖對象就是this,當前對象,哪個對象調用這個同步方法,這個同步方法使用的鎖就是哪個對象

5、如果是靜態的方法,同步方法的鎖對象就是當前類的字節碼對象,類名.class(在方法區的一個對象),哪個類在調用這個同步方法,這個同步方法使用的鎖就是哪個類的字節碼對象

代碼示例:
/*** 同步方法*/
public class Simple03 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {
?public static synchronized void print1() {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}
?public static ?synchronized void print2() {System.err.print("線");System.err.print("程");System.err.println("鎖");}
}

6、特別注意,如果是靜態方法,在靜態方法內部使用同步代碼塊,同步代碼塊鎖對象也是當前類的字節碼對象

/*** 靜態方法中使用同步代碼塊*/
public class Simple04 {public static void main(String[] args) {Printer p = new Printer();Thread t1 = new Thread() {@Overridepublic void run() {while (true) {p.print1();}}};
?Thread t2 = new Thread() {@Overridepublic void run() {while (true) {p.print2();}}};
?t1.start();t2.start();}
}
?
class Printer {public static void print1() {synchronized (Printer.class)  {System.out.print("碼");System.out.print("上");System.out.print("未");System.out.println("來");}}public static void print2() {synchronized  (Printer.class)  {System.err.print("線");System.err.print("程");System.err.println("鎖");}}
}
鎖對象的說明
  1. 線程加上同步是為了讓兩條線程相互不要影響,如果相互影響,影響的是數據的正確性

  2. 使用鎖對象,其實主要是為了鎖數據。當某條線程在操作這個數據時,其它線程不能操作當前的數據。

  3. 如果兩條線程要操作相同的數據,那么這兩條線程就需要在操作數據的部分都加上同步代碼塊,并且使用相同的鎖對象。

死鎖
  1. A線程需要甲資源,同時擁有乙資源;B線程需要乙資源,同時擁有甲資源,兩條線程都不肯釋放自己擁有的資源,同時也需要其它的資源時,就都無法進行運行。形成“死鎖”現象。

  2. 代碼表現: 有了同步代碼塊的嵌套,就可能發生死鎖。某條線程獲取了外層鎖對象A,需要內層鎖對象B,等待;

    因為另一條線程此時已經獲取了外層的鎖對象B,需要內層的鎖對象A,等待。兩條線程就會形成死鎖。

代碼示例:
/*** 死鎖*/
public class Simple05 {public static void main(String[] args) {
?Thread t1 = new Thread("蘇格拉底") {public void run() {while (true) {synchronized ("筷子A") {System.out.println("蘇格拉底爭取到了筷子A,等待筷子B");
?synchronized ("筷子B") {System.out.println("蘇格拉底獲取了所有的筷子,狂吃狂吃");}}}}};
?Thread t2 = new Thread("柏拉圖") {public void run() {while (true) {synchronized ("筷子B") {System.out.println("柏拉圖爭取到了筷子B,等待筷子A");synchronized ("筷子A") {System.out.println("柏拉圖獲取了所有的筷子,狂吃狂吃");}}}}};t1.start();t2.start();}
}
?
//  開發盡量避免鎖的嵌套以此 來避免死鎖現象發生。
// 公平鎖 非公平鎖  表鎖 行鎖 自旋鎖
火車票案例
/*** 火車票案例*/
public class Simple06 {/*** 三個窗口,同時售賣100張火車票* 打印某個窗口賣出了1張票,還剩x張* 窗口是隨機的** @param args*/public static void main(String[] args) {Tickets t1 = new Tickets();Tickets t2 = new Tickets();Tickets t3 = new Tickets();t1.setName("窗口1");t2.setName("窗口2");t3.setName("窗口3");t1.start();t2.start();t3.start();}
}
?
class Tickets extends Thread {
?//為了讓多個線程多個對象 共享同一個數據 我們加上靜態private static int tickets = 100;
?public void run() {while (true) {// 多個線程 使用同一個鎖對象  使用xx.classsynchronized (Tickets.class) {if (tickets <= 0) {break;}try {Thread.sleep(10);} catch (InterruptedException e) {e.printStackTrace();}//線程1等待 線程2等待//線程3 把ticket-- 變成0  這時候cpu切換到線程1  線程1執行--操作變為-1tickets--;System.out.println(getName() + "賣出了1張票, 還剩" + tickets + "張票");}}}
}

線程生命周期

概述
  1. 線程是一個動態的概念,有創建的時候,也有運行和變化的時候,必須也就有消亡的時候,所以從生到死就是一個生命周期。在生命周期中,有各種各樣的狀態,這些狀態可以相互轉換。

  2. 狀態羅列: 新建態:剛創建好對象的時候,剛new出來的時候 就緒態:線程準備好了所有運行的資源,只差cpu來臨 運行態:cpu正在執行的線程的狀態 阻塞態:線程主動休息、或者缺少一些運行的IO資源,即使cpu來臨,也無法運行 死亡態:線程運行完成、出現異常、調用方法結束

狀態轉換圖

image-20230103141234093

Java中線程狀態的描述
  1. 線程狀態只是理論上的狀態,在計算機系統中有更加精確的描述,可以將各種不同的狀態,封裝成對象,這些對象可能和理論狀態不完全相同。

  2. Java語言中,獲取線程狀態的方法: getState():返回當前線程對象的狀態對象;返回值類型是Thread.State,Thread的內部類

  3. Thread.State:Java語言中,描述線程狀態的類型 說明:由于狀態時有限個的,所以該類的對象,不需要手動創建,直接在類中創建好了,獲取即可,

    這種類型就是枚舉類型。每個對象,稱為枚舉項。

    Thread.State中,枚舉項:6個 NEW:新建態,沒有開啟線程 RUNNABLE:就緒態和運行態 BLOCKED:阻塞態 (等待鎖、I\O,等待鎖對象釋放了,IO資源準備完畢了,就不阻塞了) WAITING:阻塞態 (調用了wait方法,等待其它線程喚醒的狀態,沒人喊它,它就一直阻塞) TIMED_WAITING:阻塞態(調用了有時間限制的wait方法、sleep方法) TERMINATED:死亡態

代碼示例:
public class Simple01 {
?public static void main(String[] args) throws InterruptedException {//test1_new_timedWaiting_terminated_runnable();test2_blocked_TERMINATED();
?}
?//演示blocked阻塞狀態public static void test2_blocked_TERMINATED() throws InterruptedException {
?Thread t1 = new Thread("t1線程") {public void run() {synchronized ("abc") {for (int i = 0; i <= 3; i++) {//打印數字,和線程名稱System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};Thread t2 = new Thread("t2線程") {public void run() {//沒有獲取到鎖的線程對象的狀態是:BLOCKEDsynchronized ("abc") {for (int i = 0; i <= 3; i++) {System.out.println(i + "..." + getName());try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}}}};t1.start();t2.start();
?while (true) {System.out.println("t1...." + t1.getState());System.out.println("t2...." + t2.getState());Thread.sleep(50);//t1 和 t2 判斷線程是否結束if (t1.getState().equals(Thread.State.TERMINATED) &&t2.getState().equals(Thread.State.TERMINATED)) {System.out.println("兩條線程都結束了");break;}}}
?private static void test1_new_timedWaiting_runnable() throws InterruptedException {Thread t1 = new Thread() {public void run() {for (int i = 1; i < 100; i++) {System.out.println(i);try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}
?}};System.out.println("A:" + t1.getState());//NEWt1.start();System.out.println("B:" + t1.getState());//RUNNABLEThread.sleep(10);//0.01System.out.println("C:" + t1.getState());//TIMED_WAITING}
}

線程池

概述
  1. 沒有線程池的狀態:

    當使用一條線程的時候,先將線程對象創建出來,啟動線程,在運行過程中,正常完成任務之后,線程對象就結束了,就變成了垃圾對象,需要被垃圾回收器回收如果在系統中,大量的任務都是小任務,任務消耗時間較短、線程對象的創建和消亡耗費的時間比較多,

    結果:大部分的時間都浪費在了線程對象的創建和死亡上。

  2. 有線程池的狀態 在沒有任務的時候,先把線程對象準備好,存儲到一個容器中,一旦有任務來的時候,就不需要創建

    對象,而是直接將對象獲取出來執行任務如果任務破壞力較小,任務可以直接完成,這個線程對象不

    會進入死亡狀態,而是被容器回收,繼續活躍。如果任務破壞力較大,把線程任務搞死了,那么線程池

    也會繼續提供下一個線程,繼續完成這個任務。

線程池使用
  1. 步驟:獲取線程池對象;創建任務類對象;將任務類對象提交到線程池中

  2. 獲取線程池對象:

    工具類:

    //Executors:生成線程池的工具類,根據需求生成指定大小的線程池
    //ExecutorService  Executors.newSingleThreadExecutor():創建一個有單個線程的線程池
    //ExecutorService  Executors.newFixedThreadPool(int nThreads):創建一個指定線程數量的線程池    
  3. 創建任務類對象:Runnable的實現類對象,用于定義任務內容

  4. 將任務類對象提交到線程池中 ExecutorService:是一個接口,不需要手動創建這個接口的實現類對象,使用方法獲取到的就是這個接口的實現類對象,就可以調用這個接口中的方法

  5. submit(Runnable r):可以將一個任務類對象,提交到線程池中,如果有空閑的線程,就可以馬上運行這個任務,如果沒有空閑線程,那么這個任務就需要等待。

  6. shutDown():結束線程池,已經提交的全部保證完成,在此代碼后就不準繼續提交任務了

  7. shutDownNow():結束線程池,已經開始運行的,保證完成,等待的任務不保證完成

但是已經提交的,還沒有運行的,就不給運行了,作為返回值范圍;對于沒有提交的,不準提交。

代碼示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
?
public class Simple02 {public static void main(String[] args) {//  準備一個線程池對象  1個//  ExecutorService es = Executors.newSingleThreadExecutor();//  準備一個線程池對象  2個ExecutorService es = Executors.newFixedThreadPool(2);Object obj = new Object();Runnable r1 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 1; i <= 10; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r2 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 11; i <= 20; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};Runnable r3 = new Runnable() {@Overridepublic void run() {synchronized (obj) {for (int i = 21; i <= 30; i++) {System.out.println(i + "..." + Thread.currentThread().getName());try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}}}}};
?es.submit(r1);es.submit(r2);es.submit(r3);// 已經提交的全部保證完成,不準繼續提交了
?//  es.shutdown();// 已經提交的 前三個任務 保證完成,在shutdown之后就沒法添加任務了//  es.submit(r3);//  es.shutdownNow();//  shutDownNow():結束線程池,已經開始運行的,保證完成,等待的任務不保證完成//  另外結束線程池這句話遇到任務中有sleep執行的時候就會有問題,會拋異常//  可以把任務sleep的代碼注釋上 就不報錯了}
}
?

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

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

相關文章

Python 中的 queue 模塊隊列詳解;隊列如何使用——如何處理信息在多個線程間安全交換的多線程程序?

queue 模塊即隊列&#xff0c;特別適合處理信息在多個線程間安全交換的多線程程序中。下面我們對 queue 模塊進行一個詳細的使用介紹。 1 queue 模塊定義的類和異常 queue 模塊定義了以下四種不同類型的隊列&#xff0c;它們之間的區別在于數據入隊列之后出隊列的順序不同。 …

cmake編譯數據庫

在使用CMake進行編譯時&#xff0c;如果你想生成編譯數據庫&#xff0c;你可以定義CMAKE_EXPORT_COMPILE_COMMANDS選項。具體的命令如下&#xff1a; cmake -DCMAKE_EXPORT_COMPILE_COMMANDS1或者在CMakeLists.txt顯示的使能配置 set(CMAKE_EXPORT_COMPILE_COMMANDS ON)這將會…

游戲玩家升級不傷手之選,光威龍武系列超強性能

得益于國產存儲芯片的崛起&#xff0c;現在的內存條價格太香了。要放在前幾年&#xff0c;購買內存條時都會優先考慮國際一線品牌。隨著內存條行業發生巨變&#xff0c;國產品牌光威GLOWAY&#xff0c;是全球前三的內存模組廠商嘉合勁威旗下品牌&#xff0c;它推出的內存條產品…

Zebec 推出由 Visa、萬事達網絡支持的即時支付卡,加密支付新征程

“Zebec現已推出全新的加密支付卡&#xff0c;該卡由Visa、萬事達網絡支持&#xff0c;具備即時、多鏈、非托管、無需KYC、免費等特性&#xff0c;其能夠通過加密錢包與多條主流公鏈鏈接并直接調用支付&#xff0c;這將是加密支付領域的里程碑事件。” 在2023年的12月8日&#…

C++中的string容器的substr()函數

一、作用 用來截取某段字符串。 二、頭文件 #include<string> 三、參數與用法 形式&#xff1a;s.substr(pos, len) 第一個參數是想要截取的字符串初始位置&#xff0c;第二個參數是截取字符串長度。 直接來說&#xff0c;就是從s[pos]開始截一個長度為len的子串。…

【python交互界面】實現動態觀察圖像在給定HSV范圍的區域顯示

HSV顏色空間 與RGB顏色空間相比&#xff0c;HSV顏色空間更適合進行顏色分析和提取特定顏色的目標。在HSV空間中&#xff0c;顏色信息被分布在不同的通道上&#xff0c;使我們能夠更準確地定義顏色的范圍&#xff0c;并使用閾值操作輕松地分離出我們感興趣的區域部分。 HSV三個通…

二叉樹查找值為x的結點(C語言)

目錄 前言 查找值為x的結點 返回值為指針 返回值為布爾類型 整體代碼 前言 在二叉樹結點個數、葉子結點個數、樹的高度、第k層結點個數的計算&#xff08;C語言&#xff09;中&#xff0c;我們解決了關于二叉樹的部分問題&#xff0c;但是還有一個問題我們放在本篇解決。 …

數據集成和人工智能驅動的見解

數字時代使數據成為人們關注的焦點&#xff0c;將其從單純的二進制序列轉變為有價值的組織資產。隨著企業越來越多地轉向數據驅動戰略&#xff0c;數據管理的復雜性也隨之增加。當前的任務不僅僅是存儲甚至收集數據&#xff0c;而是將其轉化為可操作的情報。本博客旨在剖析尋求…

Python中的selenium安裝的步驟(瀏覽器自動化測試框架)

一、前言 我們今天要安裝的selenium 就是瀏覽器自動化測試框架&#xff0c;是一個用于Web應用程序的測試工具&#xff0c;就是模擬用戶操作。支持的瀏覽器包括Chrome&#xff0c;IE&#xff0c;Mozilla Firefox&#xff0c;Safari&#xff0c;Opera等。今天我們以Chrome為例講…

STM32單片機項目實例:基于TouchGFX的智能手表設計(2)UI交互邏輯的設計

STM32單片機項目實例&#xff1a;基于TouchGFX的智能手表設計&#xff08;2&#xff09;UI交互邏輯的設計 目錄 一、UI交互邏輯的設計 1.1 硬件平臺的資源 1.2 界面切換功能 ???????1.3 表盤界面 1.4 運動界面 ???????1.6 設置界面 ???????1.7 應…

不一樣的年會彩瞳推薦,綺芙莉多款彩瞳彰顯個性

臨近年底&#xff0c;各種公司年會、跨年晚會活動也逐漸排上日程&#xff0c;出席這種正式場合&#xff0c;每個人都有自己的“殺手锏”&#xff0c;從發型妝容到穿搭都是變美小細節&#xff0c;作為心靈之窗的雙眸&#xff0c;更需要一副彩瞳來提升我們的眼妝質感&#xff0c;…

微前端 ---- wujie-vue3 原理

目錄 前言 設置子應用? 預加載? 啟動子應用? 封裝 1.創建文件 2.安裝依賴 3.編寫組件 4.配置打包規則 5.執行打包命令 swc技術 SWC Babel Babel VS SWC 更改使用 swc 解析 使用swc 完成 esm 模式 &#xff08;export--import&#xff09; 發布到npm 更改p…

【SpringBoot】解析Springboot事件機制,事件發布和監聽

解析Springboot事件機制&#xff0c;事件發布和監聽 一、Spring的事件是什么二、使用步驟2.1 依賴處理2.2 定義事件實體類2.3 定義事件監聽類2.4 事件發布 三、異步調用3.1 啟用異步調用3.2 監聽器方法上添加 Async 注解 一、Spring的事件是什么 Spring的事件監聽&#xff08;…

持續集成交付CICD:使用Jenkins插件上傳Nexus制品

目錄 一、實驗 1.使用Jenkins插件上傳Nexus制品 一、實驗 1.使用Jenkins插件上傳Nexus制品 &#xff08;1&#xff09;Jenkins安裝插件Nexus Artifact Uploader &#xff08;2&#xff09;添加憑據 &#xff08;3&#xff09;使用片段生成器生成DSL &#xff08;4&#xf…

基于Java物業管理系統

基于Java物業管理系統 功能需求 1、房產信息管理&#xff1a;系統需要提供房產信息管理功能&#xff0c;包括房產的基本信息、租賃狀態、業主信息等。 2、報修管理&#xff1a;系統需要提供報修管理功能&#xff0c;業主可以通過系統提交報修申請&#xff0c;物業管理人員可…

docke網絡之bridge、host、none

一、bridge網絡 1.創建一個測試容器 [rootlocalhost ~]# docker run -d -it --name busybox_1 busybox /bin/sh -c "while true;do sleep 3600;done" 03b308c847edd23f21ba69afb825d92f7aaeb05b1ff4431dd47ccee439a0361a 2.查看當前機器docker有哪些網絡 [rootlocal…

C++ 訪問限定符

目錄 訪問修飾符概述 protected在類的內部和派生類中訪問調用 private在類的內部訪問和調用 訪問修飾符概述 在C中&#xff0c;有三個主要的訪問修飾符&#xff1a;public、private和protected。這些修飾符用于控制類的成員&#xff08;變量和函數&#xff09;的訪問權限。…

2023年9月8日 Go生態洞察:gopls的擴展與Go生態系統的成長

&#x1f337;&#x1f341; 博主貓頭虎&#xff08;&#x1f405;&#x1f43e;&#xff09;帶您 Go to New World?&#x1f341; &#x1f984; 博客首頁——&#x1f405;&#x1f43e;貓頭虎的博客&#x1f390; &#x1f433; 《面試題大全專欄》 &#x1f995; 文章圖文…

AI材料專題報告:AI革命催生新需求國產替代推動新方向

今天分享的AI系列深度研究報告&#xff1a;《AI材料專題報告&#xff1a;AI革命催生新需求國產替代推動新方向》。 &#xff08;報告出品方&#xff1a;光大證券&#xff09; 報告共計&#xff1a;25頁 1、算力需求增長催生 800G 光模塊需求 算力是數字經濟時代新生產力&…

2023年10月9日 Go生態洞察:深入了解類型推斷及其更多細節

&#x1f337;&#x1f341; 博主貓頭虎&#xff08;&#x1f405;&#x1f43e;&#xff09;帶您 Go to New World?&#x1f341; &#x1f984; 博客首頁——&#x1f405;&#x1f43e;貓頭虎的博客&#x1f390; &#x1f433; 《面試題大全專欄》 &#x1f995; 文章圖文…