什么是多線程?進程和線程的區別是什么?如何使用Java實現多線程?

在這里插入圖片描述

文章目錄

  • 前言
  • 我們為什么要使用線程而不是進程來實現并發編程
  • 什么是線程
  • 進程和線程的區別
  • 如何使用Java實現多線程
    • 創建線程
      • 1.創建一個繼承 Thread 類的線程類
      • 2.實現 Runnable 接口
        • 匿名內部類方式實現 Runnable 接口
        • lambda 表達式實現 Runnable 接口
    • Thread 類的常見構造方法
    • Thread 的幾個常見屬性
    • 啟動線程
    • 終止線程
      • 1.自定義標志位終止線程
      • 2.使用 Thread 自帶的標志位終止線程
    • 線程等待

前言

前面我們了解了什么是進程以及如何實現進程調度,那么今天我將為大家分享關于線程相關的知識。在學習線程之前,我們認為進程是操作系統執行獨立執行的單位,但其實并不然。線程是操作系統中能夠獨立執行的最小單元。只有掌握了什么是線程,我們才能實現后面的并發編程。

我們為什么要使用線程而不是進程來實現并發編程

實現并發編程為什么不使用多進程,而是使用多線程呢?主要體現在幾個方面:

  • 創建一個進程的開銷很大
  • 調度一個進程的開銷很大
  • 銷毀一個進程的開銷很大

開銷不僅體現在時間上,還體現在內存和 CPU 上。現在以”快“著稱的互聯網時代,這種大開銷是不受人們歡迎的。那么為什么多線程就可以實現快捷的并發編程呢?

  • 共享資源:多個線程之間共用同一部分資源,大大減少了資源的浪費
  • 創建、調度、銷毀的開銷小:相較于進程的創建、調度和銷毀,線程的創建、調度和銷毀就顯得很輕量,這樣也大大節省了時間和資源的浪費
  • 現在的計算機 CPU 大多都是多核心模式,我們的多線程模式也更能利用這些優勢

什么是線程

線程是操作系統能夠獨立調度和執行的最小執行單元。線程是進程內的一個執行流程,也可以看作是進程的子任務。與進程不同,線程在進程內部創建和管理,并且與同一進程中的其他線程共享相同的地址空間和系統資源。
只有當第一個線程創建的時候會有較大的開銷,后面線程的創建開銷就會小一點。并發編程會盡量保證每一個線程在不同的核心上單獨執行,互不干擾,但也不可避免的出現在單核處理器系統中,線程在一個 CPU 核心上運行,它們通過時間片輪轉調度算法使得多個線程輪流執行,給我們一種同時執行感覺。

線程是操作系統調度執行的基本單位

進程和線程的區別

一個進程中可以包含一個線程,也可以包含多個線程。

  1. 資源和隔離:進程是操作系統中的一個獨立執行單位,具有獨立的內存空間、文件描述符、打開的文件、網絡連接等系統資源。每個進程都擁有自己的地址空間,進程間的數據不共享。而線程是進程內的執行流程,共享同一進程的地址空間和系統資源,可以直接訪問和修改相同的數據。

  2. 創建和銷毀開銷:相對于進程,線程的創建和銷毀開銷較小。線程的創建通常只涉及創建一個新的執行上下文和一些少量的內存。而進程的創建需要分配獨立的內存空間、加載可執行文件、建立進程控制塊等操作,開銷較大。

  3. 并發性和響應性:由于線程共享進程的地址空間,多個線程可以在同一進程內并發執行任務,共享數據和通信更加方便。因此,線程的切換成本較低,可以實現更高的并發性和響應性。而進程之間通常需要進程間通信(IPC)的機制來進行數據交換和共享,開銷較大,響應性較低。

  4. 管理和調度:進程由操作系統負責管理和調度,每個進程之間是相互獨立的。而線程是在進程內部創建和管理的,線程調度和切換由操作系統的線程調度器負責。線程的調度通常比進程的調度開銷小,線程切換更快。

  5. 安全性和穩定性:由于進程之間相互獨立,一個進程的崩潰不會影響其他進程的正常運行,因此進程具有更好的安全性和穩定性。而一個線程的錯誤或異常可能會導致整個進程崩潰。

前面我們所說的 PCB 其實也是針對線程來說的,一個線程具有一個 PCB 屬性,一個進程可以含有一個或多個 PCB。

PCB 里的狀態:上下文,優先級,記賬信息,都是每個線程有自己的,各自記錄自己的,但是同一個進程里的PCB之間,pid是一樣的,內存指針和文件描述符表也是一樣的。

如何使用Java實現多線程

在Java中使用一個線程大致分為以下幾個步驟:

  1. 創建線程
  2. 啟動線程
  3. 終止線程
  4. 線程等待

創建線程

在Java中執行線程操作依賴于 Thread 類。并且創建一個線程具有多種方法。

  1. 創建一個線程類繼承自 Thread 類
  2. 實現 Runnable 接口

1.創建一個繼承 Thread 類的線程類

class MyThread extends Thread {@Overridepublic void run() {System.out.println("這是一個MyThread線程");}
}

我們需要重寫 run 方法,而 run 方法是指該線程要干什么。

創建實例對象

public class TreadDemo1 {public static void main(String[] args) {Thread t = new MyThread();}
}

2.實現 Runnable 接口

創建一個線程我們不僅可以直接創建一個繼承自 Thread 的線程類,我們也可以直接實現 Runnable 接口,因為通過源碼我們可以知道 Thread 類也實現了 Runnable 接口。
在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

我們可以將 Runnable 作為一個構造方法的參數傳進去。

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("這是一個線程");}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());}
}

但是這種實現 Runnable 接口的方式會顯得很麻煩,因為每個線程執行的內容大多是不同的,所以我們可以采用下面兩種方式來實現 Runnable 接口。

  • 匿名內部類
  • lambda 表達式

匿名內部類方式實現 Runnable 接口

public class ThreadDemo3 {public static void main(String[] args) {Thread t = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("這是一個線程");}});}
}

lambda 表達式實現 Runnable 接口

public class ThreadDemo4 {public static void main(String[] args) {Thread t = new Thread(() -> {System.out.println("這是一個線程");});}
}

Thread 類的常見構造方法

方法說明
Thread()創建對象
Thread(Runnable target)使用 Runnable 對象創建線程對象
Thread(String name)創建線程對象,并命名
Thread(Runnable target,String name)使用 Runnable 對象創建線程對象,并命名
Thread(ThreadGroup group,Runnable target線程可以被用來分組管理,分好的組即為線程組,這個我們目前了解即可

在這里插入圖片描述

Thread 類有很多構造方法,大家有興趣可以自己去看看。

Thread 的幾個常見屬性

屬性獲取方法
IDgetId()
名稱getName()
狀態getState()
優先級getPriority()
是否后臺進程isDaemon()
是否存活isAlive
是否被中斷isInterrupted()
  • ID 是線程的唯一標識,不同線程不會重復
  • 名稱是各種調試工具用到
  • 狀態表示線程當前所處的一個情況
  • 優先級高的線程理論上來說更容易被調度到
  • 關于后臺線程,需要記住一點:JVM會在一個進程的所有非后臺線程結束后,才會結束運行。
  • 是否存活,即簡單的理解,為 run 方法是否運行結束了
  • 線程的中斷問題,下面我們進一步說明

我們前面創建的都是前臺進程,我們可以感知到的,那么什么叫做后臺進程呢?

后臺進程是指在計算機系統中以低優先級運行且不與用戶交互的進程。與前臺進程相比,后臺進程在運行時不會占據用戶界面或終端窗口,并且通常在后臺默默地執行任務。

后臺進程通常用于執行系統服務、長時間運行的任務、系統維護或監控等。它們在后臺運行,不需要用戶的直接參與或操作,而且可以持續運行,即使用戶退出或注銷系統。

啟動線程

我們上面只是創建了線程,要想讓線程真正的起作用,我們需要手動啟動線程。線程對象.start()

class MyRunnable implements Runnable {@Overridepublic void run() {System.out.println("這是一個線程");}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();}
}

在這里插入圖片描述

這里有人看到輸出結果可能會問了,這跟我直接調用 run 方法好像沒什么區別吧?我們這個代碼肯定看不出來區別,所以我們稍稍修改一下代碼。

class MyRunnable implements Runnable {@Overridepublic void run() {while(true) {System.out.println("hello MyThread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}public class ThreadDemo2 {public static void main(String[] args) {Thread t = new Thread(new MyRunnable());t.start();while(true) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

這里 Thread.sleep() 的作用是使線程停止一會,防止進行的太快,我們不容易看到結果,并且這里的 Thread.sleep() 方法還需要我們拋出異常

在這里插入圖片描述

我們可以看到這里的執行結果是 main 線程和 t 線程都執行了,而不是只執行其中的一個線程。不僅如此,這兩個線程之間沒有什么規定的順序執行,而是隨機的,這種叫做搶占式執行,每個線程都會爭搶資源,所以會導致執行順序的不確定,也正是因為多線程的搶占式執行,會導致后面的線程安全問題。

那么我們再來看看,如果直接調用 run 方法,而不是 start 方法會有什么結果。

在這里插入圖片描述

當直接調用 run 方法的話,也就只會執行 t 對象的 run 方法,而沒有執行 main 方法后面的代碼,也就是說:當直接調用 run 方法的時候,線程并沒有真正的啟動,只有調用 start 方法,線程才會啟動。

我們也可以通過 Java 自帶的 jconsle 來查看當前有哪些Java進程。

我們需要找到 jconsole.exe 可執行程序。通常在這個目錄下C:\Program Files\Java\jdk1.8.0_192\bin
在這里插入圖片描述

我們也可以點進來看看。

在這里插入圖片描述

在這里插入圖片描述

終止線程

通常當主線程 main 執行完 mian 方法之后或者其他線程執行完 run 方法之后,線程就會終止,但是我們也可以在這之前手動終止線程。但是我們這里終止線程并不是立刻終止,也就相當于這里只是建議他這個線程停止,具體要不要停止得看線程的判斷。

  • 自定義標志位來終止線程
  • 使用 Thread 自帶的標志位來終止線程

1.自定義標志位終止線程

public class ThreadDemo4 {private static boolean flg = false;  //定義一個標志位public static void main(String[] args) {Thread t = new Thread(() -> {while(!flg) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();System.out.println("線程開始");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}flg = true;  /修改標志位使線程終止System.out.println("線程結束");}
}

在這里插入圖片描述

2.使用 Thread 自帶的標志位終止線程

可以使用 線程對象.interrupt() 來申請終止線程。并且使用 Thread.currentThread,isInterrupted() 來判斷是否終止線程。

  • Thread.currentThread() 獲取到當前線程對象
public class ThreadDemo4 {private static boolean flg = false;public static void main(String[] args) {Thread t = new Thread(() -> {while(!Thread.currentThread().isInterrupted()) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t.start();System.out.println("線程開始");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}t.interrupt();System.out.println("線程結束");}
}

在這里插入圖片描述

發現了沒,這里拋出了異常,但是線程并沒有終止,為什么呢?問題出在哪里呢?

其實這里問題出在 Thread.sleep 上,如果線程在 sleep 中休眠,此時調用 interrupt() 會終止休眠,并且喚醒該線程,這里會觸發 sleep 內部的異常,所以我們上面的運行結果就拋出了異常。那么為什么線程又被喚醒了呢?

interrupt 會做兩件事:

  1. 把線程內部的標志位給設置成true,也就是 !Thread.current.isInterrupt() 的結果為true
  2. 如果線程在進行 sleep ,就會觸發吟唱,把 sleep 喚醒

但是 sleep 在喚醒的時候,還會做一件事,把剛才設置的這個標志位,再設置回false(清空標志位),所以就導致了線程繼續執行。那么如何解決呢?

很簡單,因為 sleep 內部發生了異常,并且我們捕獲到了異常,所以我們只需要在 catch 中添加 break 就行了。

try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();break;}

在這里插入圖片描述

這也就相當于,我 t 線程拒絕了你的終止請求。

線程等待

在多線程中,可以使用 線程對象.join() 來使一個線程等待另一個線程執行完或者等待多長時間后再開始自己的線程。

方法說明
public void join()等待線程結束
public void join(long millis)等待線程結束,最多等 millis 毫秒
public void join(long millis,int nanos)同理,但可以更高精度
public class ThreadDemo5 {public static void main(String[] args) {Thread t = new Thread(() -> {for(int i = 0; i < 5; i++) {System.out.println("hello mythread!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}});t.start();try {t.join();} catch (InterruptedException e) {throw new RuntimeException(e);}for(int i = 0; i < 5; i++) {System.out.println("hello main!");try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}}
}

在這里插入圖片描述

在那個線程中調用的 線程對象.join() 就是哪個線程等待,而哪個線程調用 join() 方法,那么這個線程就是被等待的。而這個等待的過程也被稱為阻塞。如果在執行 join 的時候,調用 join 方法的線程如果已經結束了,那么就不會發生阻塞。

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

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

相關文章

T113-S3-RTL8211網口phy芯片調試

目錄 前言 一、RTL8211介紹 二、硬件連接 三、設備樹配置 四、內核配置 五、phy芯片配置 六、調試問題 總結 前言 在嵌入式系統開發中&#xff0c;網絡連接是至關重要的一部分。T113-S3開發板搭載了RTL8211系列的網口PHY芯片&#xff0c;用于實現以太網連接。在開發過程…

C++ QT(二)

目錄 Qt 控件按鈕QPushButton控件簡介用法示例運行效果 QToolButton控件簡介用法示例運行效果 QRadioButton控件簡介用法示例運行效果 QCheckBox控件簡介用法示例運行效果 QCommandLinkButton控件簡介用法示例運行效果 QDialogButtonBox控件簡介用法示例運行效果 輸入窗口部件Q…

用 React+ts 實現無縫滾動的走馬燈

一、走馬燈的作用 走馬燈是一種常見的網頁交互組件&#xff0c;可以展示多張圖片或者內容&#xff0c;通過自動播放或者手動切換的方式&#xff0c;讓用戶能夠方便地瀏覽多張圖片或者內容。 本次實現的不是輪播圖而是像傳送帶一樣的無限滾動的形式。 二、需求梳理 走馬燈可設…

Go Gin 中使用 JWT

一、JWT JWT全稱JSON Web Token是一種跨域認證解決方案&#xff0c;屬于一個開放的標準&#xff0c;它規定了一種Token實現方式&#xff0c;目前多用于前后端分離項目和OAuth2.0業務場景下。 二、為什么要用在你的Gin中使用JWT 傳統的Cookie-Sesson模式占用服務器內存, 拓展性…

uniapp實現自定義導航內容高度居中(兼容APP端以及小程序端與膠囊對齊)

①效果圖如下 1.小程序端與膠囊對齊 2.APP端內容區域居中 注意&#xff1a;上面使用的是colorui里面的自定義導航樣式。 ②思路&#xff1a; 1.APP端和小程序端走不同的方法&#xff0c;因為小程序端要計算不同屏幕下右側膠囊的高度。 2.其次最重要的要清晰App端和小程序端…

【數學建模】清風數模更新5 灰色關聯分析

灰色關聯分析綜述 諸如經濟系統、生態系統、社會系統等抽象系統都包含許多因素&#xff0c;系統整體的發展受各個因素共同影響。 為了更好地推動系統發展&#xff0c;我們需要清楚哪些因素是主要的&#xff0c;哪些是次要的&#xff0c;哪些是積極的&#xff0c;哪些是消極的…

網絡基礎——網絡的由來與發展史

作者&#xff1a;Insist-- 個人主頁&#xff1a;insist--個人主頁 作者會持續更新網絡知識和python基礎知識&#xff0c;期待你的關注 目錄 一、網絡的由來 二、計算機網絡的發展史 1、第一階段 2、第二階段 3、第三階段 前言 每天都是使用網絡&#xff0c;那么你知道網絡…

FPGA----Vivado SDK創建并使用靜態鏈接庫(C/C++代碼移植)

1、在進行SoC開發時&#xff0c;PS端的C/C代碼可能涉及到核心算法需要移植操作&#xff0c;為此&#xff0c;本文講述了如何將C/C代碼打包為.a文件供程序調用 2、文章以我的程序為例&#xff0c;逐步講述代碼生成靜態鏈接庫并調用的方法。 下面是我程序的目錄結構&#xff0c…

spring boot實現實體類參數自定義校驗

安裝依賴項 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency>1、新建實體類 Data public class UserEntity {private String name;private Integer age;…

RocketMQ 延遲消息

RocketMQ 延遲消息 RocketMQ 消費者啟動流程 什么是延遲消息 RocketMQ 延遲消息是指&#xff0c;生產者發送消息給消費者消息&#xff0c;消費者需要等待一段時間后才能消費到。 使用場景 用戶下單之后&#xff0c;15分鐘未支付&#xff0c;對支付賬單進行提醒或者關單處理…

PostgreSQL查詢慢sql原因和優化方案

PostgreSQL sql查詢慢優化方案有一下幾種解決方案&#xff1a; 1.關閉會話 查詢慢sql的執行會話&#xff0c;關閉進程。 查看數據庫后臺連接進程 SELECT count(*) FROM pg_stat_activity;SELECT * FROM pg_stat_activity; 查看數據庫后臺連接進程&#xff0c;但是此條SQL不…

python提取pdf圖片

import fitz import re import osdef save_pdf_img(path, save_path):path: pdf的路徑save_path : 圖片存儲的路徑# 使用正則表達式來查找圖片checkXO r"/Type(? */XObject)"checkIM r"/Subtype(? */Image)"# 打開pdfdoc fitz.open(path)# 圖片計數im…

用HARU-Net增強核分割:一種基于混合注意的殘差u塊網絡

文章目錄 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network摘要本文方法損失函數后處理消融實驗 Enhancing Nucleus Segmentation with HARU-Net: A Hybrid Attention Based Residual U-Blocks Network 摘要 核圖像分割是…

W6100-EVB-PICO 做TCP Server進行回環測試(六)

前言 上一章我們用W6100-EVB-PICO開發板做TCP 客戶端連接服務器進行數據回環測試&#xff0c;那么本章將用開發板做TCP服務器來進行數據回環測試。 TCP是什么&#xff1f;什么是TCP Server&#xff1f;能干什么&#xff1f; TCP (Transmission Control Protocol) 是一種面向連…

zabbix監控安裝部署

目錄 一、環境 二、配置 1.配置yum源&#xff0c;這里用的清華的 2.過濾一下安裝包&#xff0c;查看依賴包 安裝依賴包 3.配置數據庫 開機自啟 創建數據庫 創建用戶 授權 導入數據到數據庫 查看zabbix數據庫有沒有表和數據 4.修改zabbix配置文件 1.修改zabbix配置…

去趨勢化一個心電圖信號、信號功率譜、低通IIR濾波器并平滑信號、對濾波器引起的延遲進行補償研究(Matlab代碼實現)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;歡迎來到本博客????&#x1f4a5;&#x1f4a5; &#x1f3c6;博主優勢&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客內容盡量做到思維縝密&#xff0c;邏輯清晰&#xff0c;為了方便讀者。 ??座右銘&a…

SPM實現framework自動管理和分發

一、前言 Swift Package Manager (SPM) 是蘋果官方提供的用于管理 Swift 項目的依賴關系和構建過程的工具。它是一個集成在 Swift 編程語言中的包管理器&#xff0c;用于解決在開發過程中管理和構建包依賴項的需求。 那么如何使用SPM管理和分發Objective C編寫的二進制庫呢&a…

HOT86-單詞拆分

leetcode原題鏈接&#xff1a;單詞拆分 題目描述 給你一個字符串 s 和一個字符串列表 wordDict 作為字典。請你判斷是否可以利用字典中出現的單詞拼接出 s 。注意&#xff1a;不要求字典中出現的單詞全部都使用&#xff0c;并且字典中的單詞可以重復使用。 示例 1&#xff1a…

不同路徑 II——力扣63

class Solution {public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int n=

一鍵登錄是如何在登錄方式中脫穎而出的?

首先&#xff0c;我們先了解一下登錄方式的演變過程&#xff0c;大致可以分為三個階段。分別是賬號密碼登錄、短信驗證碼登錄和一鍵登錄。 階段一&#xff1a;賬號密碼登錄 賬號密碼登錄是一種常見的用戶身份驗證方式&#xff0c;用戶需要輸入一個唯一的賬號和對應的密碼來登…