Tread多線程

来源:https://www.cnblogs.com/yaomagician/archive/2023/03/08/17195966.html
-Advertisement-
Play Games

Tread多線程 什麼是線程? 線程(Thread)是一個程式內部的一條執行流程。 程式中如果只有一條執行流程,那這個程式就是單線程的程式。 多線程是什麼? 多線程是指從軟硬體上實現的多條執行流程的技術(多條線程由cpu負責調度執行)。 多線程的創建方式 方式一:繼承Thread ①定義一個子類My ...


Tread多線程

什麼是線程?

  • 線程(Thread)是一個程式內部的一條執行流程。

  • 程式中如果只有一條執行流程,那這個程式就是單線程的程式。

多線程是什麼?

多線程是指從軟硬體上實現的多條執行流程的技術(多條線程由cpu負責調度執行)。

多線程的創建方式

方式一:繼承Thread

①定義一個子類MyThread繼承線程類java.lang.Thread,重寫run()方法

②創建MyThread類的對象

③調用線程對象的start()方法啟動線程(啟動後還是執行run方法的)

//(1)讓自定義的MyThread繼承Thread線程類【自定義的類也就具備線程的特性】
public class MyThread extends Thread {
    //(2)想要聲明自定義的線程執行的時候到底執行什麼代碼,主動重寫父類的run方法
    @Override
    public void run() {
        for (int i = 1; i <= 20; i++) {
            System.out.println("【自定義線程】的run方法執行了第" + i + "次!");
        }
    }
}
public class ThreadTest1 {
    public static void main(String[] args) {
        //(3)創建自定義線程類對象並調用start方法啟動線程
        MyThread myThread = new MyThread();
        myThread.start();
​
        //補:在自定義線程啟動之後,繼續編寫代碼讓主線程執行
        for (int i = 1; i <= 20; i++) {
            System.out.println("【主線程】的run方法執行了第" + i + "次!");
        }
    }
}

 

方式一優缺點

  • 優點:編碼簡單

  • 缺點:線程類已經繼承Thread,無法繼承其他類,不利於功能的擴展。

方式二:實現Runnable介面

①定義一個線程任務類MyRunnable實現Runnable介面,重寫run()方法

②創建MyRunnable任務對象

③把MyRunnable任務對象交給Thread處理。

public class ThreadTest2 {
    public static void main(String[] args) {
        //(3)創建MyRunnable線程任務對象 【和線程還沒有關係】
        MyRunnable myRunnable = new MyRunnable();
        //(4)創建Thread線程對象,並且線程任務作為構造方法的參數傳遞【槍:√ 彈夾:√】
        Thread t = new Thread(myRunnable);
        t.start();
​
        //補:在自定義線程啟動之後,繼續編寫代碼讓主線程執行
        for (int i = 1; i <= 20; i++) {
            System.out.println("【主線程】的run方法執行了第" + i + "次!");
        }
    }
}

 

方式二優缺點:

  • 優點:任務類只是實現介面,可以繼續繼承其他類,實現其他介面,擴展性強。

  • 缺點:需要多一個Runnable對象。

前兩種線程創建文件都存在的一個問題

假如線程執行完畢後有一些數據需要返回,他們重寫的run方法均不能直接返回結果。

解決(多線程的第三種創建方式)

利用Callable,FutureTask類型實現。

①創建任務對象

定義一個類實現Callable介面,重寫call方法,封裝要做的事情和要返回的數據。

把Callable類型的對象封裝成FutureTask對象(線程任務對象)。

②把線程任務對象封裝成Thread對象。

③調用Thread對象的start方法啟動線程。

④線程執行完畢後,通過FutureTask對象的的get方法去獲取線程任務執行的結果。

public class ThreadTest4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //(3)Thread類不支持直接傳遞一個Callable線程任務對象【封裝FutureTask對象並且將Callable線程任務作為構造參數傳遞】
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
​
        //(4)創建Thread類對象並且將FutureTask作為參數傳遞
        Thread t = new Thread(futureTask);
        t.start();
​
        //★(5)通過futureTask對象獲取結果
        Integer result = futureTask.get();
        System.out.println("帶有返回值的線程任務執行完成後返回的結果是:" + result);
​
​
        //補:在自定義線程啟動之後,繼續編寫代碼讓主線程執行
        for (int i = 1; i <= 20; i++) {
            System.out.println("【主線程】的run方法執行了第" + i + "次!");
        }
    }
}

 

方式三優缺點:

  • 優點:線程任務類只是實現介面,可以繼續繼承類和實現介面,擴展性強;可以線上程執行完畢後去獲取線程執行的結果。

  • 缺點:編碼複雜一點。

三種線程創建方法比較

 

 

 

Thread的常見用法

public void run()             線程的任務方法

public void start()           啟動線程

public Strign getName()         獲取1當前線程名稱,線程名稱預設是Thread-索引

public void setName(String name)     為線程設置名稱

public static Thread currentThread()   獲取當前執行的線程對象

public static void sleep(long time)    讓當前執行的線程休眠多少毫秒後,再繼續執行

public void join()            讓調用這個方法的線程先執行完

public class MyRunRunable implements Runnable {
    @Override
    public void run() {
        for (int i = 1; i <= 200; i++) {
            //在列印的時候,想要【獲取到執行當前這行代碼的線程】的線程名稱
            //通過★Thread.currentThread():獲取當前執行此方法的線程對象
            System.out.println(Thread.currentThread().getName() + "已經跑了" + i + "米!");
        }
    }
}
​
​
public class ThreadTest5 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
​
        String threadName = Thread.currentThread().getName();
        System.out.println("【主線程名稱】:" + threadName);
​
        MyRunRunable myRunRunable = new MyRunRunable();
        //線程起名方式(1):通過線程對象調用setName方法傳遞名稱
        Thread t1 = new Thread(myRunRunable);
        t1.setName("張二狗");
        //思考:模擬兩個人跑 => 兩個線程跑【跑的邏輯一樣 所以使用同一個線程任務】不會幹擾【底層:線程棧 線程執行過程中產生的變數數據都線上程棧中保存】
//線程起名方式(2):通過new Thread構造方法的時候,將參數一作為線程任務,參數二作為線程名稱
        Thread t2 = new Thread(myRunRunable, "劉鐵柱");
        t1.start();
        //t1.join(); 【讓調用此方法的線程先執行完:插隊】
        t2.start();
    }
}

 

線程安全

什麼是線程安全問題?

多個線程,同時操作同一個共用資源的時候,可能會出現業務安全問題。

取錢的線程安全問題

場景:小明和小紅是一對夫妻,他們有一個共同的賬戶,餘額是10萬元,如果小明和小紅同時來取錢,並且2人各自都在取錢10萬元,可能會出現什麼問題呢?

 

 

 

線程安全問題出現的原因?

  • 存在多個線程在同時執行

  • 多個線程同時訪問一個共用資源

  • 存在修改共用資源的情況

 

定義一個賬號類

package com.itheima.safe;
​
import java.time.LocalTime;
​
public class Account {
    private String accoundId;
    private Integer money;
    //取錢:takeMoney
    public void takeMoney(Integer money) {
        //獲取當前取錢的線程名稱
        String name = Thread.currentThread().getName();
        System.out.println(LocalTime.now() + " " + name + "準備開始取錢!");
        if (this.money >= money) {
            System.out.println(LocalTime.now() + " " + name + "取出了" + money + "元!");
            this.money -= money;
        } else {
            System.out.println(LocalTime.now() + " " + name + "餘額不足!");
        }
        System.out.println(LocalTime.now() + " 賬戶的餘額是:" + this.money + "元!");
    }
​
    public String getAccoundId() {
        return accoundId;
    }
​
    public void setAccoundId(String accoundId) {
        this.accoundId = accoundId;
    }
​
    public Integer getMoney() {
        return money;
    }
​
    public void setMoney(Integer money) {
        this.money = money;
    }
​
    public Account() {
    }
​
    public Account(String accoundId, Integer money) {
        this.accoundId = accoundId;
        this.money = money;
    }
}

 


定義線程類


public class TakeMoneyRunnable implements Runnable {
    //線程任務需要訪問到Account賬戶對象【將賬戶對象作為線程任務的構造方法 並且只給出一個有參構造】
    private Account account;
    public TakeMoneyRunnable(Account account) {
        this.account = account;
    }
    @Override
    public void run() {
        account.takeMoney(100000);
    }
}

 

 

測試類

package com.itheima.safe;
​
public class TakeMoneyThreadTest {
    public static void main(String[] args) {
        Account account = new Account("CHINA-BANK-62261728738", 100000);
        //創建線程任務【由於兩個線程的邏輯一樣 只需要一個線程任務】
        TakeMoneyRunnable takeMoneyRunnable = new TakeMoneyRunnable(account);
        //創建線程對象並且傳遞線程任務和線程名稱
        Thread t1 = new Thread(takeMoneyRunnable, "張二狗");
        Thread t2 = new Thread(takeMoneyRunnable, "王美麗");
        t1.start();
        t2.start();
    }
}
​

 

線程同步(解決線程安全)

線程同步的思想

讓多個線程實現先後依次訪問共用資源,這樣就解決了安全問題。

同步代碼塊

作用:把訪問共用資源的核心代碼給上鎖,以此保證線程安全。

 

 

 

原理:每次只允許一個線程加鎖後進入,執行完畢後自動解鎖,其他線程次才可以來執行。

對線程安全改造

 public void takeMoney(Integer money) {
        String name = Thread.currentThread().getName();
        System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢");
       //(同步代碼塊)加鎖
        synchronized (this) {
            if (this.money >= money) {
                System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元");
                this.money -= money;
            } else {
                System.out.println(LocalDateTime.now() + "" + name + "餘額不足");
            }
            System.out.println(LocalDateTime.now() + "賬戶的餘額是" + this.money + "元");
        }
    }

 

同步鎖的註意事項

  • 對於當前同時執行的線程來說,同步鎖必須是同一把鎖(同一個對象),否則會出bug。

鎖對象的使用規範

  • 建議使用共用資源作為鎖對象,對於實例方法建議使用this作為鎖對象。

  • 對於靜態方法建議使用位元組碼(類名.class)對象作為鎖對象。

同步方法

作用:把訪問共用資源的核心代碼給上鎖,以此保證線程安全。

 

 

 

原理:每次只允許一個線程加鎖後進入,執行完畢後自動解鎖,其他線程次才可以來執行。

對線程安全改造

//同步方法加鎖(修飾符後面,放回值類型前面)
    public synchronized void takeMoney(Integer money) {
        String name = Thread.currentThread().getName();
        System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢");
​
            if (this.money >= money) {
                System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元");
                this.money -= money;
            } else {
                System.out.println(LocalDateTime.now() + "" + name + "餘額不足");
            }
            System.out.println(LocalDateTime.now() + "賬戶的餘額是" + this.money + "元");
    }
}

 

同步方法底層原理

  • 同步方法其實底層也是有隱式鎖對象的,只是鎖的範圍是整個方法代碼。

  • 如果方法是實例方法:同步方法預設用this作為的鎖對象。

  • 如果方法是靜態方法:同步方法預設用類名.class作為的鎖對象。

1.同步方法是如何保證線程安全的?

  • 對出現問題的核心方法使用**synchronized修飾**

  • 每次只能一個線程占鎖進入訪問

2.同步方法的同步鎖對象的原理?

  • 對於實例方法預設使用**this作為鎖對象。**

  • 對於靜態方法預設使用**類名.class對象作為鎖對象。**

Lock鎖

Lock鎖是JDK5開始提供的一個新的鎖定操作,通過它可以創建出鎖對象進行加鎖和解鎖,更靈活、更方便、更強大。

Lock是介面,不能直接實例化,可以採用它的實現類ReentrantLock來構建Lock鎖對象。

對線程安全改造

private static final Lock LOCK = new ReentrantLock();
    public void takeMoney(Integer money) {
​
        LOCK.lock();
​
        try {
            String name = Thread.currentThread().getName();
            System.out.println(LocalDateTime.now() + "" + name + "準備開始取錢");
            if (this.money >= money) {
                System.out.println(LocalDateTime.now() + "" + name + "取出了" + money + "元");
                this.money -= money;
            } else {
                System.out.println(LocalDateTime.now() + "" + name + "餘額不足");
            }
            System.out.println(LocalDateTime.now() + "賬戶的餘額是" + this.money + "元");
        }finally {
            LOCK.unlock();
        }
        }
​

 

線程池

瞭解線程池

什麼是線程池

線程池就是一個可以復用線程的技術。

不使用線程池的問題

用戶每發起一個請求,後臺就需要創建一個新線程來處理,下次新任務來了肯定又要創建新線程處理,而創建新線程的開銷是很大的,並且請求過多時,肯定會產生大量的線程,這樣會嚴重影響系統的性能。

線程池的工作原理

 

 

 

創建線程池

如何得到線程池對象?

方式一:使用ExecutorService的實現類ThreadPoolExecutor創建一個線程池對象。

 

 

 

方式二:使用Executors(線程池的工具類)調用方法返回不同特點的線程池對象。

ThreadPoolExecutor**構造器**

 

 

 

  • 參數一:corePoolSize : 指定線程池的核心線程數量。

  • 參數二:maximumPoolSize:指定線程池的最大線程數量。

  • 參數三:keepAliveTime :指定臨時線程的存活時間。

  • 參數四:unit:指定臨時線程存活的時間單位(秒、分、時、天)

  • 參數五:workQueue:指定線程池的任務隊列。

  • 參數六:threadFactory:指定線程池的線程工廠。

  • 參數七:handler:指定線程池的任務拒絕策略(線程都在忙,任務隊列也滿了的時候,新任務來了該怎麼處理)

public class PoolDemo1 {
    public static void main(String[] args) {
        //基於ThreadPoolExecutor的構造方法創建線程池對象
        //核心線程:【線程任務:Cpu密集型(運算):當前機器Cpu的核心數+1 Runtime.getRuntime().availableProcessors()+1】
        //核心線程:【線程任務:IO密集型(讀寫):當前機器Cpu的核心數*2 Runtime.getRuntime().availableProcessors()*2】
        //ThreadFactory:線程工廠 【Exectors.defaultThreadFactory】 獲取預設的線程工廠
        ThreadPoolExecutor pool = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors() + 1, 15, 40L, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
​
        //可以基於線程池規範介面的execute方法提交線程任務交給線程池執行
        for (int i = 1; i <= 25; i++) {
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "執行了線程任務!");
                }
            });
        }
​
        //AbortPolicy:預設丟棄新任務並且拋出異常
        //DiscardPolicy:預設丟棄新任務並且不拋出異常
        //DiscardOldestPolicy:預設將等待時間最長的任務丟棄,並且讓新任務添加到隊列中
        //CallerRunsPolicy:使用主線程執行新任務繞過當前線程池
//線程池一旦提交任務就持久運行【想要關閉調用shutdown/shutdownNow】
        pool.shutdown();
    }
}

 

線程池的註意事項

1、臨時線程什麼時候創建?

新任務提交時發現核心線程都在忙,任務隊列也滿了,並且還可以創建臨時線程,此時才會創建臨時線程。

2、什麼時候會開始拒絕新任務?

核心線程和臨時線程都在忙,任務隊列也滿了,新的任務過來的時候才會開始拒絕任務。

線程池如何處理Runnable任務?

  • 使用ExecutorService的方法:

  • void execute(Runnable target)

線程池如何處理Callable任務,並得到任務執行完後返回的結果?

  • 使用ExecutorService的方法:

  • Future<T> submit(Callable<T> command)

併發,並行

併發的含義

進程中的線程是由CPU負責調度執行的,但CPU能同時處理線程的數量有限,為了保證全部線程都能往前執行,CPU會輪詢為系統的每個線程服務,由於CPU切換的速度很快,給我們的感覺這些線程在同時執行,這就是併發。

並行的理解

在同一個時刻上,同時有多個線程在被CPU調度執行。

簡單說說多線程是怎麼執行的?

  • 併發:CPU分時輪詢的執行線程。

  • 並行:同一個時刻多個線程同時在執行。

 

線程的生命周期

Java線程的狀態

  • Java總共定義了6種狀態

  • 6種狀態都定義在Thread類的內部枚舉類中。

 

 

 

 

 


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

-Advertisement-
Play Games
更多相關文章
  • w3c機構:規定網頁分成三個部分:結構、樣式、表現形式。 無序列表: (每個列表左側都有實心黑點,可以用css去掉) <ul> <li>列表1</li> <li>列表2</li> </ul> 快捷方式:ul>li*數量 tab <ul></ul>中只能嵌套<li></li> <li></li>之間相 ...
  • 這篇文章主要描述如何在使用消息隊列時避免丟消息,包括檢測消息丟失的方法以及消息從生產到完成消費的過程中,經歷的生產、存儲和消費這三個階段是分別如何保證消息可靠傳遞的。 ...
  • 一、文件系統存儲 電腦剛開始出現的時候,那時候沒有硬碟,只有記憶體,數據不會進行存儲,一般只用於科技計算,計算完輸出結果後,程式就撤出記憶體了。後來隨著技術發展,有了硬碟、文件,在文件的基礎上有了文件系統。文件系統可以滿足數據存放和查找的需求。 文件系統作為資料庫用了一段時間,當數據越來越多、規模越來 ...
  • 這篇文章描述如何使用消息隊列中的事務消息機制實現分散式事務。事務消息適用於需要非同步更新數據,並且對數據實時性要求不太高的場景。 ...
  • ##一、通訊錄準備 #####1. 通訊錄信息的準備 #####2. 通訊錄功能的框架 #####3. 文件安排 ##二、實現通訊錄的功能 #####1. 添加功能 #####2. 刪除功能 #####3. 展示功能 #####4. 更改功能 #####5. 查找功能 #####6. 排序功能 ## ...
  • C++中的仿函數(function object)是一個重載了函數調用運算符(operator())的類或結構體,在使用時可以像函數一樣調用。通過仿函數,C++程式員可以更加靈活地實現自己的演算法。 ...
  • 緩存的重要性 簡而言之,緩存的原理就是利用空間來換取時間。通過將數據存到訪問速度更快的空間里以便下一次訪問時直接從空間里獲取,從而節省時間。 我們以CPU的緩存體係為例: CPU緩存體系是多層級的。分成了CPU -> L1 -> L2 -> L3 -> 主存。我們可以得到以下啟示。 越頻繁使用的數據 ...
  • 前言: Servlet 容器初始化 Servlet 時,會為這個 Servlet 創建一個 ServletConfig 對象,並將 ServletConfig 對象作為參數傳遞給 Servlet 。通過 ServletConfig 對象即可獲得當前 Servlet 的初始化參數信息。一個 Web 應 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...