Java 線程創建與常用方法

来源:https://www.cnblogs.com/lcha-coding/archive/2022/06/04/16341851.html
-Advertisement-
Play Games

## 進程與線程的區別 - 進程基本上相互獨立的,而線程存在於進程內,是進程的一個子集 - 進程擁有共用的資源,如記憶體空間等,供其內部的線程共用 - 進程間通信較為複雜 - 同一臺電腦的進程通信稱為 IPC(Inter-process communication) - 不同電腦之間的進程... ...


進程與線程

進程

  • 程式由指令和數據組成,但這些指令要運行,數據要讀寫,就必須將指令載入至 CPU,數據載入至記憶體。在指令運行過程中還需要用到磁碟、網路等設備。進程就是用來載入指令、管理記憶體、管理 IO 的
  • 當一個程式被運行,從磁碟載入這個程式的代碼至記憶體,這時就開啟了一個進程

線程

  • 一個進程之內可以分為一到多個線程。
  • 一個線程就是一個指令流,將指令流中的一條條指令以一定的順序交給 CPU 執行
  • Java 中,線程作為最小調度單位,進程作為資源分配的最小單位。 在 windows 中進程是不活動的,只是作為線程的容器

進程與線程的區別

  • 進程基本上相互獨立的,而線程存在於進程內,是進程的一個子集
  • 進程擁有共用的資源,如記憶體空間等,供其內部的線程共用
  • 進程間通信較為複雜
    • 同一臺電腦的進程通信稱為 IPC(Inter-process communication)
    • 不同電腦之間的進程通信,需要通過網路,並遵守共同的協議,例如 HTTP
  • 線程通信相對簡單,因為它們共用進程內的記憶體,一個例子是多個線程可以訪問同一個共用變數
  • 線程更輕量,線程上下文切換成本一般上要比進程上下文切換低

並行與併發

單核 cpu 下,線程實際還是 串列執行 的。操作系統中有一個組件叫做任務調度器,將 cpu 的時間片(windows下時間片最小約為 15 毫秒)分給不同的程式使用,只是由於 cpu 線上程間(時間片很短)的切換非常快,人類感覺是 同時運行的 。總結為一句話就是: 微觀串列,巨集觀並行 。一般會將這種 線程輪流使用 CPU 的做法稱為併發 (concurrent)

多核 cpu下,每個 核(core) 都可以調度運行線程,這時候線程可以是並行的。

Java 線程

創建和運行線程

  • 直接使用 Thread

    package create;
    
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.ThreadCre")
    public class ThreadCre {
        public static void main(String[] args) {
    
            Thread t = new Thread(){
                @Override
                public void run() {
                    log.debug("running");
                }
            };
    
            t.start();
    
            log.debug("running");
    
        }
    }
    
  • 使用 Runnable 配合 Thread

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.RunnableCre")
    public class RunnableCre {
        public static void main(String[] args) {
            Runnable r = new Runnable() {
                @Override
                public void run() {
                    log.debug("running");
                }
            };
    
            Thread t = new Thread(r,"t2");
    
            t.start();
        }
    }
    

    使用 lambda 方式簡化

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    @Slf4j(topic = "c.RunnableCre")
    public class RunnableCre {
        public static void main(String[] args) {
            Runnable r = () -> { log.debug("running"); };
    
            Thread t = new Thread(r,"t2");
    
            t.start();
        }
    }
    
  • FutureTask 配合 Thread

    package create;
    
    import lombok.extern.slf4j.Slf4j;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    @Slf4j(topic = "c.FutureTaskCre")
    public class FutureTaskCre {
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            FutureTask<Integer> task = new FutureTask<>(new Callable<Integer>() {
                @Override
                public Integer call() throws Exception {
                    log.debug("running...");
                    Thread.sleep(1000);
                    return 100;
                }
            });
    
            Thread t = new Thread(task,"t1");
            t.start();
    
            log.debug("{}",task.get());
        }
    }
    

Thread 與 Runnable 的關係

  • 用 Runnable 更容易與線程池等高級 API 配合
  • 用 Runnable 讓任務類脫離了 Thread 繼承體系,更靈活

線程運行的原理

棧與棧幀

每個線程啟動後,虛擬機就會為其分配一塊棧記憶體。

  • 每個棧由多個棧幀(Frame)組成,對應著每次方法調用時所占用的記憶體
  • 每個線程只能有一個活動棧幀,對應著當前正在執行的那個方法

線程上下文切換

因為以下一些原因導致 cpu 不再執行當前的線程,轉而執行另一個線程的代碼

  • 線程的 cpu 時間片用完
  • 垃圾回收
  • 有更高優先順序的線程需要運行
  • 線程自己調用了 sleep、yield、wait、join、park、synchronized、lock 等方法

當 Context Switch 發生時,需要由操作系統保存當前線程的狀態,並恢復另一個線程的狀態,Java 中對應的概念就是程式計數器(Program Counter Register),它的作用是記住下一條 jvm 指令的執行地址,是線程私有的

  • 狀態包括程式計數器、虛擬機棧中每個棧幀的信息,如局部變數、操作數棧、返回地址等
  • Context Switch 頻繁發生會影響性能

常見方法

方法名 static 功能說明 註意
start() 啟動一個新線程,在新的線程運行 run 方法中的代碼 start 方法只是讓線程進入就緒,裡面的代碼不一定立刻運行(CPU的時間片還沒有分給它)。每個線程對象的 start 方法只能調用一次,否則會出現異常
run() 新線程啟動後會調用的方法 如果在構造 Thread 對象時傳遞了 Runnable 參數,則線程啟動後會調用 Runnable 中的 run 方法。但可以創建 Thread 的子類對象來覆蓋預設行為
join() 等待線程運行結束
join(long n) 等待線程運行結果,最多等待 n 毫秒
getId() 獲取線程長整型的 id
getName() 獲取線程名
setName(String) 修改線程名
getPriority() 獲取線程優先順序
setPriority(int) 修改線程優先順序 java中規定線程優先順序是1~10 的整數,較大的優先順序能提高該線程被 CPU 調度的機率
getState() 獲取線程狀態 Java 中線程狀態是用 6 個 enum 表示,分別為:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED
isInterrupted() 判斷是否被打斷 不會清除 打斷標記
isAlive() 線程是否存活(還沒有運行完畢)
interrupt() 打斷線程 如果被打斷線程正在 sleep,wait,join 會導致被打斷的線程拋出 InterruptedException,並清除 打斷標記 ;如果打斷的正在運行的線程,則會設置 打斷標記 ;park 的線程被打斷,也會設置 打斷標記
interrupted() static 判斷當前線程是否被打斷 會清除 打斷標記
currentThread() static 獲取當前正在執行的線程
sleep(long n) static 讓當前執行的線程休眠 n 毫秒,休眠時讓出 CPU 的時間片給其他程式
yield() static 提示線程調度器讓出當前線程對CPU的使用 主要是為了測試和調試

start 與 run

調用 run

public static void main(String[] args) {
 	Thread t1 = new Thread("t1") {
 		@Override
 		public void run() {
 			log.debug(Thread.currentThread().getName());
 			FileReader.read(Constants.MP4_FULL_PATH);
 		}
 	};
    
 	t1.run();
 	log.debug("do other things ...");
}

輸出

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

程式仍在 main 線程運行, FileReader.read() 方法調用還是同步的

總結

  • 直接調用 run 是在主線程中執行了 run,沒有啟動新的線程
  • 使用 start 是啟動新的線程,通過新的線程間接執行 run 中的代碼

sleep 與 yield

sleep

  • 調用 sleep 會讓當前線程從 Running 進入 Timed Waiting 狀態(阻塞)
  • 其它線程可以使用 interrupt 方法打斷正在睡眠的線程,這時 sleep 方法會拋出 InterruptedException
  • 睡眠結束後的線程未必會立刻得到執行(搶占時間片)
  • 建議用 TimeUnit 的 sleep 代替 Thread 的 sleep 來獲得更好的可讀性

yield

  • 調用 yield 會讓當前線程從 Running 進入 Runnable 就緒狀態,然後調度執行其它線程
  • 具體的實現依賴於操作系統的任務調度器

線程優先順序

  • 線程優先順序會提示(hint)調度器優先調度該線程,但它僅僅是一個提示,調度器可以忽略它
  • 如果 cpu 比較忙,那麼優先順序高的線程會獲得更多的時間片,但 cpu 閑時,優先順序幾乎沒作用

join

等待一個線程執行結束

等待多個線程的結果

情況一:

package testJoin;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {

    static int r = 0 , r1 = 0 , r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test2();
    }

    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                r1 = 10;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
                r1 = 20;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        log.debug("join begin");
        t1.join();
        log.debug("t1 join end");
        t2.join();
        log.debug("t2 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
    }
}

輸出:

14:18:02 [main] c.demo1 - join begin
14:18:03 [main] c.demo1 - t1 join end
14:18:04 [main] c.demo1 - t2 join end
14:18:04 [main] c.demo1 - r1: 20 r2: 0 cost: 2008

情況二:

package testJoin;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {

    static int r = 0 , r1 = 0 , r2 = 0;

    public static void main(String[] args) throws InterruptedException {
        test2();
    }

    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                r1 = 10;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                Thread.sleep(2000);
                r1 = 20;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        long start = System.currentTimeMillis();
        t1.start();
        t2.start();
        log.debug("join begin");
        t2.join();
        log.debug("t2 join end");
        t1.join();
        log.debug("t1 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}",r1,r2,end-start);
    }
}

輸出:

14:19:19 [main] c.demo1 - join begin
14:19:21 [main] c.demo1 - t2 join end
14:19:21 [main] c.demo1 - t1 join end
14:19:21 [main] c.demo1 - r1: 20 r2: 0 cost: 2006

另外 join 也可以帶參數,是有時效的等待。當到設定時間線程還未給出結果,直接向下運行,不再等待。如果設定時間還沒到但是線程已經執行完畢,則直接向下執行,不再等待。

interrupt

打斷 sleep,wait,join 的線程

這幾個方法都會讓線程進入阻塞狀態

打斷 sleep 的線程, 會清空打斷狀態,以 sleep 為例

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo1")
public class demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000);
                //註意:sleep,wait,join等被打斷並以異常形式表現出來後
                // 會把打斷標記重新置為 false(未打斷狀態)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");

        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打斷標記:{}",t1.isInterrupted());
    }
}

輸出:

15:08:12 [t1] c.demo1 - sleep...
15:08:13 [main] c.demo1 - interrupt
15:08:13 [main] c.demo1 - 打斷標記:false
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at testInterrupt.demo1.lambda$main$0(demo1.java:11)
	at java.lang.Thread.run(Thread.java:748)

Process finished with exit code 0

打斷正常運行的線程打斷標記置為:true

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo2")
public class demo2 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true){
                boolean interrupted = Thread.currentThread().isInterrupted();
                if(interrupted){
                    log.debug("被打斷了,退出迴圈");
                    break;
                }
            }
        },"t1");
        t1.start();

        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
    }
}

輸出:

15:17:40 [main] c.demo2 - interrupt
15:17:40 [t1] c.demo2 - 被打斷了,退出迴圈

打斷 park 線程

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j(topic = "c.demo4")
public class demo4 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("打斷狀態:{}",Thread.currentThread().isInterrupted());
        },"t1");

        t1.start();

        Thread.sleep(1000);
        t1.interrupt();
    }
}

輸出:

14:16:21 [t1] c.demo4 - park...
14:16:22 [t1] c.demo4 - unpark...
14:16:22 [t1] c.demo4 - 打斷狀態:true

兩階段終止模式

package testInterrupt;

import lombok.extern.slf4j.Slf4j;

@Slf4j(topic = "c.demo3")
public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}

@Slf4j(topic = "c.TwoPhaseTermination")
class TwoPhaseTermination{
    private Thread monitor;

    //啟動監控線程
    public void start(){
        monitor = new Thread(() -> {
            while (true){
                Thread current = Thread.currentThread();
                if(current.isInterrupted()){
                    log.debug("料理後事");
                    break;
                }
                try {
                    Thread.sleep(1000);//情況1
                    log.debug("執行監控記錄");//情況2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //重新設置打斷標記
                    current.interrupt();
                }
            }
        });

        monitor.start();
    }

    //終止監控線程
    public void stop(){

        monitor.interrupt();
    }
}

輸出:

15:33:02 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:03 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
15:33:04 [Thread-0] c.TwoPhaseTermination - 執行監控記錄
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at testInterrupt.TwoPhaseTermination.lambda$start$0(demo3.java:29)
	at java.lang.Thread.run(Thread.java:748)
15:33:04 [Thread-0] c.TwoPhaseTermination - 料理後事

Process finished with exit code 0

不推薦的方法

還有一些不推薦使用的方法,這些方法已過時,容易破壞同步代碼塊,造成線程死鎖

方法名 static 功能說明
stop() 停止線程運行
suspend() 掛起(暫停)線程運行
resume() 恢複線程運行

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

-Advertisement-
Play Games
更多相關文章
  • GPU 渲染機制:CPU計算好顯示內容提交到GPU,GPU渲染完成後將渲染結果放入幀緩衝區frame buffer,隨後視頻控制器會按照VSync信號逐行讀取幀緩衝區的數據,經過可能的數模轉換傳遞給顯示器顯示。 GPU 屏幕渲染有以下兩種方式: ● 1)On-Screen Rendering,意為當 ...
  • 非常感謝小趙同學給我反饋的這個 Bug
  • 前言 眾所周知,spring對於java程式員來說是一個及其重要的後端框架,幾乎所有的公司都會使用的框架,而且深受廣大面試官的青睞。所以本文就以常見的一個面試題"spring bean的生命周期"為切入點,從源碼的角度帶領大家來看一看 spring bean到底是如何創建的 。spring bean ...
  • springboot自動裝配原理探究 結論: SpringBoot啟動會載入大量的自動配置類 我們看我們需要的功能有沒有在SpringBoot預設寫好的自動配置類當中; 我們再來看這個自動配置類中到底配置了哪些組件;(只要我們要用的組件存在在其中,我們就不需要再手動配置了) 給容器中自動配置類添加組 ...
  • vscode 中配置Java環境 轉載說明:本篇文檔原作者[@火星動力猿],文檔出處來自嗶哩嗶哩-【教程】VScode中配置Java運行環境 轉載請在開頭或顯眼位置標註轉載信息。 1.下載VScode 官網地址:https://code.visualstudio.com/ (點鏈接時按下Ctrl,不 ...
  • SpringMVC 是基於 MVC 開發模式的框架,用來優化控制器,是 Spring 家族的一員,同時它也具備 IOC 和 AOP ...
  • 一、填空題 在種群增長預測問題中,若資源環境等因素是有限的,則應使用的微分方程模型為 Logistic模型 某種群分為 4 個年齡組, 各組的繁殖率分別為 0, 0.8, 1.8, 0.2, 存活率分別為 0.5, 0.7, 0.9, 0. 現各組的數量均為 100, 則該種群的的穩定分佈向量為 解 ...
  • 一、Postman Postman 是一個款 HTTP 請求模擬工具 首先演示一下 Postman 最基本的使用,創建一個 Spring Boot 項目,測試的代碼如下: import org.springframework.web.bind.annotation.GetMapping; impor ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...