java線程詳解

来源:https://www.cnblogs.com/star95/archive/2023/07/26/17583193.html
-Advertisement-
Play Games

# java線程詳解 ## 線程 ### 概念 說到線程,就不得不提進程,為什麼呢,因為進程是操作系統進行分配資源和調度的最小單位,比如windows系統安裝的應用軟體(office、qq、微信等)啟動時,由操作系統協調分配資源和調度執行稱之為一個進程,進程間是相互獨立和隔離的。而線程是進程最小執行 ...


java線程詳解

線程

概念

說到線程,就不得不提進程,為什麼呢,因為進程是操作系統進行分配資源和調度的最小單位,比如windows系統安裝的應用軟體(office、qq、微信等)啟動時,由操作系統協調分配資源和調度執行稱之為一個進程,進程間是相互獨立和隔離的。而線程是進程最小執行單位,一個進程的執行過程中可以有多個線程,這樣可以發揮多核CPU的能力,提高執行效率。
java中的線程不是由操作系統直接調度,而且通過java虛擬機與操作系統進行指令交互完成。所以對於java程式員來說,使用線程非常簡單,只需要在語言層面編寫完代碼,交給虛擬機運行,剩下的臟活累活在底層就由java虛擬機完成,使用線程一時爽,一直使用一直爽(哈哈,雖然多線程能充分壓榨CPU,但是用不好的話也會產生許多問題,比如併發導致的數據錯誤、系統負載飆升等)。

基本用法

有兩種方式來創建線程
1、一種是實現Runnable介面,然後利用Thread類的構造函數傳入Runnable介面創建Thread實例。
2、另外一種是繼承Thread

1、實現Runnable

class WorkerThread1 implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "執行完成");
    }
}

2、繼承Thread

class WorkerThread2 extends Thread {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "執行完成");
    }
}

測試用例:

public static void main(String[] args) {
    test1();
}

private static void test1() {
    new Thread(new WorkerThread1(), "t1").start();
    Thread t2 = new WorkerThread2();
    t2.setName("t2");
    t2.start();
}

輸出:
註意這裡的執行順序不一定是t1先輸出,也可能是t2先輸出,因為線程啟動後只是準備就緒,最終需要等待操作系統調度執行才能執行。

t1執行完成
t2執行完成

這兩種方式都可以創建線程並執行,細心的讀者可能看過源碼發現其實Thread類也實現了Runnable介面。如果自己的類已經繼承了別的類,那麼可以實現Runnable介面創建線程,否則可以繼承Thread類覆寫run()方法即可。

註意:要讓線程執行需要調用start()方法,這樣虛擬機才能創建一個線程等待操作系統調度,直接執行run()方法則是在當前線程直接調用該方法,同步執行,不會再創建線程。

線程狀態及流轉

線程的生命周期可用狀態表示,總共有6種狀態,
Thread類的源碼里我們可以看到有個枚舉類State

public enum State {
    // 新建狀態,被new出來後,還未調用start方法
    NEW,
    // 可運行狀態,線程已就緒,獲取到CPU資源就運行,運行中就是Running狀態
    RUNNABLE,
    // 阻塞狀態,比如在等待鎖對象,或者讀取流等待
    BLOCKED,
    // 等待狀態,比如在等待鎖對象,需要被notify喚醒處於就緒狀態,獲取到CPU資源就運行
    WAITING,
    // 帶有超時時間的等待,時間過後自動返回繼續執行,比如sleep或者wait(long time)
    TIMED_WAITING,
    // 終止狀態,自然停止或者拋出異常停止
    TERMINATED;
}

狀態流轉圖:

屬性及方法

屬性

常用並且需要關註的屬性如下:

private int priority;// 線程優先順序,1<=priority<=10,優先順序越大可能最終被操作系統優先調度的幾率越大,但不一定會被先調用
private boolean daemon = false;// 是否後臺線程,當所有的非後臺線程結束時,所有的後臺線程才結束,比如垃圾回收線程就是後臺線程,負責在程式運行過程中,清理掉不在使用的對象
private Runnable target;// 目標類,要使用線程執行的業務邏輯寫在run()方法里
private ThreadGroup group;// 線程組,所有的線程都必須屬於某一個線程組,預設的是父線程組,未創建線程組時即main線程組
ThreadLocal.ThreadLocalMap threadLocals = null;// 線程獨享本地變數表

ThreadLocal不瞭解的童鞋可以參考ThreadLocal源碼分析

構造方法

其實最終都會調到一個init的方法,這裡會初始化一些線程的基本信息。

ThreadGroup g:屬於的線程組(每個線程必須屬於一個線程組,沒有設置線程組的話這裡會預設使用父線程組,即main線程組)
Runnable target:要執行的方法
String name:線程名字,可單獨設置,預設使用的是`Thread-自增序號`
long stackSize:這隻線程占用的棧大小,一般不設置,由java虛擬機決定
AccessControlContext acc:這是一種安全訪問機制,一般不用自己設置。

常用方法

// jvm方法,獲取當前正在執行的線程
public static native Thread currentThread();
// 調用該方法所在的線程通知讓出CPU資源,由操作系統重新分配線程執行,有可能還是該線程繼續執行
public static native void yield();
// 線程休眠millis毫秒,讓出CPU資源,但是不會釋放持有的對象鎖,當被中斷時拋出異常
public static native void sleep(long millis) throws InterruptedException;
// 啟動線程,更合理的說法是線程準備就緒,等待最終操作系統調度執行
public synchronized void start()
// 中斷線程,其實只是設置中斷標誌為true,如果線程已經終止,調用該方法不會有任何作用,線程自身和安全驗證通過的線程才有許可權調該線程的這個方法,否則會返回異常SecurityException,還有以下幾種特殊場景。
// 1、如果當前線程處理wait()、join()、sleep(long)方法時中斷標誌將被清除,並且拋出異常InterruptedException。(清除中斷標誌是因為拋出了異常後可由程式控制執行,中斷標誌沒啥意義了)
// 2、繼承了InterruptibleChannel如果IO阻塞狀態,則channel會關閉並且中斷標誌設置為true,拋出異常ClosedByInterruptException
// 3、如果該線程阻塞在Selector,則中斷標誌設置為true並且select會立即返回一個非0的值表示select方法執行了
public void interrupt()
// 返回當前線程是否處於中斷狀態,並清除當前運行的線程的中斷標誌。
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}
// 返回當前線程是否處於中斷狀態,不會清除當前運行的線程的中斷標誌。線程終止後該方法始終返回false
public boolean isInterrupted() {
   return isInterrupted(false);
}

常用場景

重點看下interrupt()interrupted()isInterrupted()這三個方法的使用。
1、不使用變數終止線程
下麵示例調用了interrupt()方法

public class ThreadTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println(Thread.currentThread().getName() + ":" + System.currentTimeMillis());
                }
            }
        }, "t1");
        t.start();
        //主線程休2秒
        Thread.sleep(2000);
        t.interrupt();
        System.out.println(Thread.currentThread().getName() + "運行結束");
    }
}

程式運行部分結果

t1:1584607075440
t1:1584607075440
t1:1584607075440
t1:1584607075440
main運行結束
t1:1584607077740
t1:1584607077740
t1:1584607077740
t1:1584607077740
t1:1584607077741
t1:1584607077741
t1:1584607077741

這裡我們看到調用了interrupt()方法,但是t線程並沒有終止,因為這個方法只是設置中斷標誌為true,線程是否結束跟這個標誌沒有關係,需要其他邏輯判斷。
如果我們把程式的while迴圈的條件調整如下這樣,那麼t線程調用isInterrupted()方法獲取到中斷標誌為true,!true條件結果為false所以while迴圈結束,線程自然終止。

while (true) 
改成
while (!Thread.currentThread().isInterrupted())

2、線程組的操作
線程組主要是可以管理線程或線程組,一個大型的線程組看起來像是一棵樹,可方便的對線程和子線程組進行監控和操作,比如統計活躍線程和線程組,或者中斷和銷毀線程組操作等。

public static void main(String[] args) {
    ThreadGroup tg1 = new ThreadGroup("tg1");
    ThreadGroup tg2 = new ThreadGroup("tg2");
    ThreadGroup tg3 = new ThreadGroup(tg2, "tg3");
    Thread t1 = new WorkerThread(tg1, "t1");
    Thread t2 = new WorkerThread(tg2, "t2");
    Thread t3 = new WorkerThread(tg1, "t3");
    Thread t4 = new WorkerThread(tg3, "t4");
    t1.start();
    t2.start();
    t3.start();
    t4.start();
    System.out.println("tgName:" + tg1.getName() + "\tactiveCount:" + tg1.activeCount());
    System.out.println("tgName:" + tg2.getName() + "\tactiveGroupCount:" + tg2.activeGroupCount());
    System.out.println("tgName:" + tg3.getParent().getName() + "\tactiveGroupCount:" + tg3.activeGroupCount());
}
    
class WorkerThread extends Thread {
    public WorkerThread(ThreadGroup tg, String name) {
        super(tg, name);
    }
    @Override
    public void run() {
        while (true) {
        }
    }
}

輸出結果如下:

tgName:tg1	activeCount:2
tgName:tg2	activeGroupCount:1
tgName:tg2	activeGroupCount:0

總結

線程的使用可以提高系統的處理能力,比較複雜的業務可以使用多線程協作縮短執行時間,線程的結束不能僅僅依靠中斷,需要根據業務實現自己的邏輯。另外引入多線程會增加系統的複雜度,線程的管理問題也會比較麻煩,同時線程數過多會頻繁的讓CPU切換,增加系統負載,這種時候可以選擇使用線程池來解決問題,後續會再詳細介紹。


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

-Advertisement-
Play Games
更多相關文章
  • 這裡給大家分享我在網上總結出來的一些知識,希望對大家有所幫助 1. 以下是一些有關使用Canvas的技巧: 繪製基本形狀:Canvas可以用於繪製各種基本形狀,如矩形、圓形、線條等。使用 fillRect() 方法繪製矩形,使用 arc() 方法繪製圓形,使用 lineTo() 方法繪製線條等。 繪 ...
  • 通過本文的學習,你已經瞭解了Flutter的高級主題,包括處理用戶交互、創建動畫以及訪問網路數據等。這些知識將幫助你更深入地掌握Flutter的開發能力,為你的應用添加更多功能和交互體驗。希望本文對你的Flutter學習之旅有所幫助,祝你在Flutter的世界中取得更多成功! ...
  • >我們是[袋鼠雲數棧 UED 團隊](http://ued.dtstack.cn/),致力於打造優秀的一站式數據中台產品。我們始終保持工匠精神,探索前端道路,為社區積累並傳播經驗價值。 >本文作者:空山 # 前言 > 由於筆者最近在開發中遇到了一個重覆渲染導致子組件狀態值丟失的問題,因此關於性能優化 ...
  • ### 寫在前面 前面的文章中提到過,自己開始在博客園上更新文章。 說也奇怪,自己博客園賬號註冊了好久,都沒在上面更新過博客。 直到前段時間博客園的求助信息火了,才對博客園有了全新的認知。 博客園一個最大的特點就是簡潔、乾凈,廣告少。 但也有一個個人認為很不好的地方就是界面太醜,容易勸退新人。 直到 ...
  • 沒有足夠的特征數據,安全策略將是"無根之木,無源之水"。微信安全數據倉庫應運而生,成為整個安全業務的特征數據存儲中心,每天服務了萬億級的特征數據讀寫請求,為整個微信安全策略提供了可靠的數據支撐,是微信安全基石之所在。然而,微信安全數據倉庫不僅僅是一個存儲中心,更是一個特征管理和數據質量管理的中心。在... ...
  • ## 3.1、創建module #### 3.1.1、右擊project,創建新module ![image](https://img2023.cnblogs.com/blog/2052479/202307/2052479-20230725081202352-22924479.png) ### 3. ...
  • # 包 go程式由一個個不同的包組成,程式的入口是名為main的包,比如我們創建一個main文件 main.go ```go package main import "fmt" func main(){ fmt.Println("hello") } ``` go要求每一個go文件的非註釋開頭必須是` ...
  • 到目前為止,我們只討論了使用Docker來部署應用程式。然而,Docker也是一個極好的用於開發應用程式的工具。可以採用一些不同的建議來改善開發體驗。 - 在應用程式中使用`docker-compose`以方便開發。 - 使用綁定掛載將本地代碼掛載到容器文件系統中,以避免每次更改都需要重新構建容器映 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...