JavaSE:多線程詳解筆記

来源:https://www.cnblogs.com/wutong666/archive/2023/03/22/17245263.html
-Advertisement-
Play Games

JavaSE:多線程學習 01 初識進程 1.1 Process & Thread 1、首先簡要介紹程式。程式是指令和數據的有序集合,其本身沒有任何運行的含義,只是一個靜態的概念。 2、進程則是執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位。 3、通常在一個進程中可以包含若幹線程。線 ...


JavaSE:多線程學習

01 初識進程

1.1 Process & Thread

1、首先簡要介紹程式。程式是指令和數據的有序集合,其本身沒有任何運行的含義,只是一個靜態的概念。

2、進程則是執行程式的一次執行過程,是一個動態的概念。是系統資源分配的單位。

3、通常在一個進程中可以包含若幹線程。線程是CPU調度和執行的單位。

PS:很多線程是模擬出來的,真正的多線程是指有多個CPU,即多核,如伺服器。如果是模擬出來的多線程,即在只有一個CPU的情況下,在同一個時間點,CPU只能執行一條代碼。由於切換速度很快,所以會出現同時運行的錯覺。

4、線程:

  • 線程就是獨立的執行路徑;
  • 在程式運行時,哪怕沒有手動創建線程,後臺也會有多個線程,如主線程,gc線程(垃圾回收);
  • main()稱之為主線程,為系統的入口,用於執行整個程式;
  • 在一個進程中,如果開闢了多個線程,線程的運行由調度器(CPU)安排調度。調度器是與操作系統緊密相關的,先後順序是不能人為干預的;
  • 對同一份資源進行操作時,會出現資源爭奪,需要加入併發控制。
  • 線程會帶來額外的花銷,如CPU調度時間,併發控制消耗。
  • 每個記憶體在自己的工作記憶體交互,記憶體控制不當會出現數據不一致。

02 創建線程

2.1 多線程有三種創建方式

1、Thread class(通過繼承Thread類)

2、Runnable介面(實現Runnable介面)

3、Callable介面(實現Callable介面)

2.2 Thread類

1、自定義線程類繼承Thread類

2、重寫run()方法,編寫線程執行體

3、在別的類中創建該線程單位,調用start()方法啟動多線程。

2.1-2.2小結

繼承Thread類

  • 子類繼承Thread類具備多線程能力

  • 啟動線程:thread類.start()

  • 但不建議使用,避免OOP單繼承的特性

實現Runnable介面

  • 實現介面Runnable具備多線程能力
  • 啟動線程:傳入目標對象+thread類.start()
  • 推薦使用:避免單繼承的局限性,具有很強的靈活性,方便同一個對象被多個線程使用

2.3 Callable介面

1、實現Callable,需要返回值類型

2、重寫Call方法,需要拋出異常

3、創建目標對象

4、創建執行服務

ExecutorService ser = Executors.newFixedThreadPool(線程數量)

5、提交執行

Future<Boolean> 線程名 = ser.submit(對象名);

6、獲取返回值

返回值類型 返回值名稱 = 線程名.get();

7、服務關閉

ser.shutdown();

2.4 靜態代理

1、真實對象和代理對象要實現同一介面

2、代理對象要代理真實角色

好處:

代理對象可以做很多真實角色做不了的事情

真實對象可以專註於自己的事

2.5 Lambda表達式

1、為什麼要使用Lambda表達式

  • 避免匿名內部類定義過多
  • 可以使得代碼更加簡潔
  • 去掉大部分沒有意義的代碼,只留下最核心的內部邏輯

2、Lambda表達式的核心是採取函數式編程思想。因此,理解Function Interface(函數式介面)是學習Lambda表達式的關鍵。

3、函數式介面的定義:

  • 任何一個介面,如果它只包含一個抽象方法,那它就是一個函數式介面。
public interface Runnable{
    public abstract void run();
}
  • 對於函數式介面,可以採取Lambda表達式的方法創建相關對象。

Lambda表達式使用註意:

  • 只有無參Lambda表達式可以寫成如下模式:
new Thread(()->{語句});
  • Lambda表達式只能在有一行代碼的情況下才能簡化成一行,如果有多行需要用代碼塊包裹
對象名 = 參數->{語句1;語句;}
  • 使用lambda表達式一定要註意是針對函數式介面,即介面中只有一個抽象方法
  • 如果lambda表達式中有多個參數,需要註意格式的統一,即如果有類型就必須都有類型,如果沒有就全都去掉

03 線程狀態

3.1 線程狀態簡介

!

3.1.1 線程停止

1、建議線程正常停止,限制次數,不要使用死迴圈
2、建議使用標誌位,即外部調整標誌位停止線程運行
3、不要使用stop或destroy等過時或者JDK不推薦的方法

3.1.2 線程休眠

  • sleep(時間)是指當前線程的阻塞秒數;

  • sleep存在異常InterruptedException;

  • sleep時間到達後線程進入就緒狀態

  • sleep可以模擬網路延時和倒計時等(巧妙運用sleep可以發現程式中併發多線程可能存在的問題)

  • 每一個對象都有一個鎖,延時不會釋放鎖

3.1.3 線程禮讓

  • 線程禮讓是指讓正在運行的線程暫停,但不阻塞

  • 保存線程運行斷點,回到就緒狀態

  • 讓CPU重新調度,不一定能禮讓成功。

3.1.4 線程合併

  • Join方法合併線程,待此線程執行結束後,再執行其他線程,會導致其他線程阻塞
  • 可以想象成插隊

3.2 線程狀態

3.2.1 線程狀態觀測

  • 線程可以處於以下狀態之一:
    • NEW
      尚未啟動的線程處於此狀態。
    • RUNNABLE
      在Java虛擬機中執行的線程處於此狀態。
    • BLOCKED
      被阻塞等待監視器鎖定的線程處於此狀態。
    • WAITING
      正在等待另一個線程執行特定動作的線程處於此狀態。
    • TIMED_WAITING
      正在等待另一個線程執行動作達到指定等待時間的線程處於此狀態。
    • TERMINATED
      已退出的線程處於此狀態。

3.2.2 線程優先順序

  • Java提供一個線程調度器來監控程式中啟動後進入就緒狀態的所有線程,線程調度器按照優先順序決定哪個線程優先執行

  • 線程優先順序用數字來表示,範圍從1-10

    • Thread.MIN_PRIORITY = 1;
      
    • Thread.MAX_PRIORITY = 10;
      
    • Thread.NORM_PRIORITY = 5;
      
  • 使用以下方法來改變或者獲取優先順序

    • getPriority()   setPriority(inx XXX)
      
      
  • 備註:1、優先順序的設定建議在start()之前

​ 2、優先順序低只是意味著獲得調度的概率低,並不是優先順序低一定就會被後調用。歸根結底還是要看CPU的管理。

3.2.3 守護線程和用戶線程

  • 線程分為用戶線程和守護線程
  • JVM虛擬機必須確保用戶線程執行完畢
  • 但是虛擬機不必等待守護線程執行結束
  • 如:後臺監控記憶體,後臺記錄操作日誌,垃圾回收(gc線程)等

3.3 線程同步

3.3.1 線程同步簡介

1、併發:同一個對象被多個線程同時操作

2、處理多線程時,多個線程訪問同一個對象,並且某些線程還想修改這個對象,這時候就需要線程同步。線程同步實際上是一種等待機制,多個需要訪問此對象的線程進入這個對象的等待池形成隊列,等待前麵線程使用完畢,再調用下一個線程。

3、隊列和鎖:

​ 由於同一進程的多個線程共用同一塊存儲空間,在帶來方便的同時,也帶來了訪問衝突的問題,為了保證數據在方法中被訪問的正確性,在訪問時加入鎖機制synchronized。當一個線程獲得對象的排它鎖時,就會獨占相關資源,其他線程必須等待該線程運行完畢釋放鎖。但由此會帶來一些問題:

  • 一個線程持有鎖會導致其他所有需要此鎖的線程掛起。
  • 在多線程競爭下,加鎖、釋放鎖會導致比較多的上下文切換和調度延時,從而引起性能降低。
  • 如果一個優先順序高的線程等待一個優先順序低的線程釋放鎖,就會引起優先順序倒置,引發性能問題

3.3.2 三大不安全案例

3.3.2.1 系統購票

(詳見代碼)

3.3.2.2 銀行取錢

(詳見代碼)

3.3.2.3 線程安全性

(詳見代碼)

3.3.3 同步方法和同步塊

  • 由前面可知,Java可以利用private關鍵字來保證數據對象只能被方法,因此仿照這個可以針對多線程併發提出一套機制,這套機制就是synchronized關鍵字。它包括兩種用法:synchronized方法和synchronized塊。
3.3.3.1 同步方法
  • 同步方法

    public synchronized void method(int args){}
    
  • synchronized方法控制對”對象“的訪問,每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖才能執行,否則線程會阻塞。方法一旦執行,就獨占該鎖,直到該方法返回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行。

    • 需要註意:若將一個大的方法申明為synchronized將會嚴重影響程式效率!

    • 此外,synchronized方法針對的是方法中的this類屬性。

  • 方法裡面需要修改的內容才需要鎖;鎖的數量太多會影響系統運行效率。

3.3.3.2 同步塊
  • 同步塊

    synchronized (obj){}
    
  • obj稱之為同步監視器

    • Obj可以是任何對象,但是推薦使用共用資源作為同步監視器
  • 同步方法中無需指定同步監視器,因為同步方法的同步監視器就是this,就是這個對象本身,或者是class。

  • 同步監視器的執行過程:

    • 第一個線程訪問,鎖定同步監視器,執行其中代碼。

    • 第二個線程訪問,發現同步監視器被鎖定,無法訪問。

    • 第一個線程訪問完畢,解鎖同步監視器。

    • 第二個線程訪問,發現同步監視器沒有鎖,然後鎖定並訪問。

3.3.4 初識JUC

(詳見代碼)

3.4 死鎖和Lock鎖

  • 死鎖

  • Lock鎖

    • 從JDK5.0開始,Java提供了更加強大的線程同步機制——通過顯示定義同步鎖對象來實現同步。

    • 同步鎖使用Lock對象充當java.until.concurrent.Lock介面,是控制多個線程對共用資源進行訪問的工具。鎖提供了對共用資源的獨占訪問,每次只能有一個線程對Lock對象加鎖。線程開始訪問共用資源之前應當先獲得Lock對象

    • ReentrantLock類實現了Lock,它擁有與synchronized相同的併發性和記憶體語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以顯示加鎖、釋放鎖。

    • class A{
          private final ReentrantLock lock = new ReentrantLock();
          public void m(){
                lock.lock();
              try{
                    //保證線程安全的代碼;
              }finally{
                   lock.unlock();
                  //如果同步代碼有異常,要將unlock()寫入finally語句塊
              }
          }
      }
      
  • synchronized與Lock的對比

    • Lock是顯示鎖(手動開啟和關閉鎖,別忘記關閉鎖)synchronized是隱式鎖,出了作用域自動釋放(代碼塊或是方法)

    • Lock只有代碼塊鎖,synchronized有代碼塊和方法鎖

    • 使用Lock鎖,JVM將花費更少的時間來調度線程,性能更好。並且具有更好的擴展性(提供更多的子類)

    • 優先使用順序:

      • Lock>同步代碼塊(已經進入了方法體,分配了相應資源)>同步方法(在方法體之外)

04 線程協作

4.1 線程通信問題

  • 應用場景:生產者和消費者問題

    • 假設倉庫中存有一定量的物品,生產者將生產出的產品放入倉庫,消費者消費倉庫中的產品;

    • 如果倉庫中沒有物品,生產者將生產出的產品放入倉庫,消費者停止消費等待倉庫中有物品;

    • 如果倉庫中物品已滿,生產者停止生產並等待,消費者消費倉庫中的產品。

  • 這是一個線程同步問題,生產者和消費者共用同一個資源,並且生產者和消費者互為依賴,互為條件。

    • 對於生產者,沒有生產產品前,要通知消費者等待,而生產了產品之後,又需要馬上通知消費者消費。

    • 對於消費者,在消費之後,要通知生產者已經結束消費,需要生產新的產品以供消費。

    • 在生產者消費者問題中,僅有synchronized是不夠的

      • synchronized可阻止併發更新同一個共用資源,實現了同步

      • synchronized不能用來實現不同線程之間的信息傳遞(通信)

  • Java提供了幾個方法解決線程之間的通信問題

    方法名 作用
    wait() 表示線程會一直等待,直到其他線程通知。與sleep不同,會釋放鎖
    wait(long timeout) 指定等待的毫秒數
    notify() 喚醒一個處於等待狀態的線程
    notifyAll() 喚醒同一個對象上所有調用wait()方法的線程,優先順序別高的優先被調用
    • 註意:這些均是Object類的方法,都只能在同步方法或者同步代碼塊中使用,否則會拋出異常IllegalMonitorStateException

4.2 解決辦法一:管程法

  • 併發協作模型”生產者/消費者模式“--->管程法

    • 生產者:負責生產數據的模塊(可能是方法,對象,線程,進程);

    • 消費者:負責處理數據的模塊(可能是方法,對象,線程,進程);

    • 緩衝區:消費者不能直接使用生產者的數據,他們之間有個“緩衝區”;

  • 生產者將生產好的數據放入緩衝區,消費者從緩衝區拿出數據。

4.3 解決辦法二:信號燈法

  • 信號燈法其實就是利用一個外部標誌位,結合wait()方法控制整個線程的運行

4.4 解決辦法三:線程池

  • JDK5.0開始提供了線程相關的API:ExcutorServiceExecutor

  • ExcutorService:真正的線程池介面。常見子類ThreadPoolExcutor

  • void execute(Runnable command):執行任務/命令,沒有返回值,一般用來執行Runnable

  • Futuresubmit(Callabletask):執行任務,有返回值,一般用來執行Callable

  • void shutdown():關閉連接池

  • Executors:工具類、線程池的工廠類,用於創建並返回不同類型的線程池

05 總結


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

-Advertisement-
Play Games
更多相關文章
  • 模板方法模式(Template Method Pattern):定義一個行為的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個行為的結構即可重定義該行為的某些特定步驟。 這些步驟被稱為“具體操作”(Concrete Operations),而整個行為的結構和順序則被稱為“模板方法”(T ...
  • 設計模式是個老生常談的話題, 不同的人對此有不同的看法 新手可能會覺得設計模式難以理解, 並且也只與編程語言有關, 或者只與前端後端有關, 適用範圍很窄 或者就乾脆覺得這玩意兒沒啥卵用, 寫了那麼一大坨只是讓代碼變得複雜難懂, 不如直接複製黏貼刪刪改改來得方便 老手可能會覺得設計模式就是yyds, ...
  • 1. 測試真實的應用程式 1.1. 應該以實際產品的使用方式進行測試 1.2. 所有的基準測試通常都包括一個預熱期,在這期間,JVM可以將代碼編譯到最佳狀態 1.3. 微基準測試(microbenchmark) 1.3.1. 通過測量一小部分代碼的性能來確定多種實現中哪個最好 1.3.2. 必須讀取 ...
  • 1.模塊(Module)和包(Package) 1.1 理解模塊(Module) 理解為是一個py文件 module是組織單位,它自己獨立構成一個命名空間,它本身是一個Python object 在Python object裡面,還可以有很多其他的Python object 實際應用中, modul ...
  • SpringBoot異常處理 1.基本介紹 預設情況下,SpringBoot提供/error處理所有錯誤的映射,也就是說當出現錯誤時,SpringBoot底層會請求轉發到/error這個映射路徑所關聯的頁面或者控制器方法。(預設異常處理機制) 要驗證這個點,我們只需要設置一個攔截器,當每次請求時都在 ...
  • 線程同步 線程安全 要保證線程安全有兩個前提: 程式調用了多線程。 多個線程操作共同的變數 以上兩個條件滿足後,程式就有可能觸犯線程不安全的問題 什麼是線程不安全? 舉例說明:假如一場演唱會需要售賣門票,有三個售票口,A,B,C。它們會同時售票,假如一共只有100張票,那麼當100張票售賣完後,售票 ...
  • 職位一 社招雲計算測試開發工程師 崗位職責: 1、負責雲計算產品的測試設計和測試開發工作,包括計算,存儲,網路等方向; 2、包括但不限於功能、性能、可靠性、用戶體驗等系統性測試; 3、通過技術手段,建設雲計算質量體系,包括產品的功能、性能、質量過程數據,保證產品的穩定性; 4、負責雲計算產品的質量推 ...
  • 前提 小白一個,啥都不會,歡迎指點。 題目 隨機生成10個整數(1-100的範圍),保存到數組,並倒序列印以及求平均值,求最大值和最大值的下標,並查找裡面知否有8。 思路 隨機生成-->採用random(),註意範圍在( 1-100) 。 求取最大值下標插入索引 在再次建立一個索引,以此判斷隨機生成 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...