線程sleep,wait,notify,join,yield方法解析

来源:https://www.cnblogs.com/starry-skys/archive/2020/02/29/12386722.html
-Advertisement-
Play Games

線程的五種狀態 線程從創建到銷毀一般分為五種狀態,如下圖: 1) 新建 當用new關鍵字創建一個線程時,就是新建狀態。 2) 就緒 調用了 start 方法之後,線程就進入了就緒階段。此時,線程不會立即執行run方法,需要等待獲取CPU資源。 3) 運行 當線程獲得CPU時間片後,就會進入運行狀態, ...


線程的五種狀態

線程從創建到銷毀一般分為五種狀態,如下圖:

1) 新建

當用new關鍵字創建一個線程時,就是新建狀態。

2) 就緒

調用了 start 方法之後,線程就進入了就緒階段。此時,線程不會立即執行run方法,需要等待獲取CPU資源。

3) 運行

當線程獲得CPU時間片後,就會進入運行狀態,開始執行run方法。

4) 阻塞

當遇到以下幾種情況,線程會從運行狀態進入到阻塞狀態。

  • 調用sleep方法,使線程睡眠。
  • 調用wait方法,使線程進入等待。
  • 當線程去獲取同步鎖的時候,鎖正在被其他線程持有。
  • 調用阻塞式IO方法時會導致線程阻塞。
  • 調用suspend方法,掛起線程,也會造成阻塞。

需要註意的是,阻塞狀態只能進入就緒狀態,不能直接進入運行狀態。因為,從就緒狀態到運行狀態的切換是不受線程自己控制的,而是由線程調度器所決定。只有當線程獲得了CPU時間片之後,才會進入運行狀態。

5) 死亡

當run方法正常執行結束時,或者由於某種原因拋出異常都會使線程進入死亡狀態。另外,直接調用stop方法也會停止線程。但是,此方法已經被棄用,不推薦使用。

線程常用方法

1)sleep

當調用 Thread.sleep(long millis) 睡眠方法時,就會使當前線程進入阻塞狀態。millis參數指定了線程睡眠的時間,單位是毫秒。 當時間結束之後,線程會重新進入就緒狀態。

註意,如果當前線程獲得了一把同步鎖,則 sleep方法阻塞期間,是不會釋放鎖的。

2) wait、notify和notifyAll

首先,它們都是Object類中的方法。需要配合 Synchronized關鍵字來使用。

調用線程的wait方法會使當前線程等待,直到其它線程調用此對象的notify/notifyAll方法。 如果,當前對象鎖有N個線程在等待,則notify方法會隨機喚醒其中一個線程,而notifyAll會喚醒對象鎖中所有的線程。需要註意,喚醒時,不會立馬釋放鎖,只有當前線程執行完之後,才會把鎖釋放。

另外,wait方法和sleep方法不同之處,在於sleep方法不會釋放鎖,而wait方法會釋放鎖。wait、notify的使用如下:

public class WaitTest {
    private static Object obj = new Object();

    public static void main(String[] args) throws InterruptedException {
        ListAdd listAdd = new ListAdd();

        Thread t1 = new Thread(() -> {
            synchronized (obj){
                try {
                    for (int i = 0; i < 10; i++) {
                        listAdd.add();
                        System.out.println("當前線程:"+Thread.currentThread().getName()+"添加了一個元素");
                        Thread.sleep(300);
                        if(listAdd.getSize() == 5){
                            System.out.println("發出通知");
                            obj.notify();
                        }
                    }
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (obj){
                try {
                    if(listAdd.getSize() != 5){
                        obj.wait();
                    }
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println("線程:"+Thread.currentThread().getName()+"被通知.");
            }

        });

        t2.start();
        Thread.sleep(1000);
        t1.start();
    }
}

class ListAdd {
    private static volatile List<String> list = new ArrayList<String>();

    public void add() {
        list.add("abc");
    }

    public int getSize() {
        return list.size();
    }
}

以上,就是創建一個t2線程,判斷list長度是否為5,不是的話,就一直阻塞。然後,另外一個t1線程不停的向list中添加元素,當元素長度為5的時候,就去喚醒阻塞中的t2線程。

然而,我們會發現,此時的t1線程會繼續往下執行。直到方法執行完畢,才會把鎖釋放。t1線程去喚醒t2的時候,只是讓t2具有參與鎖競爭的資格。只有t2真正獲得了鎖之後才會繼續往下執行。

3) join

當線程調用另外一個線程的join方法時,當前線程就會進入阻塞狀態。直到另外一個線程執行完畢,當前線程才會由阻塞狀態轉為就緒狀態。

或許,你在面試中,會被問到,怎麼才能保證t1,t2,t3線程按順序執行呢。(因為,我們知道,正常情況下,調用start方法之後,是不能控制線程的執行順序的)

咳咳,當前青澀的我,面試時就被問到這個問題,是一臉懵逼。其實,是非常簡單的,用join方法就可以輕鬆實現:

public class TestJoin {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new MultiT("a"));
        Thread t2 = new Thread(new MultiT("b"));
        Thread t3 = new Thread(new MultiT("c"));
        
        t1.start();
        t1.join();

        t2.start();
        t2.join();

        t3.start();
        t3.join();
    }

}

class MultiT implements Runnable{
    private String s;
    private int i;

    public MultiT(String s){
        this.s = s;
    }

    @Override
    public void run() {
        while(i<10){
            System.out.println(s+"===="+i++);
        }
    }
}

最終,我們會看到,線程會按照t1,t2,t3順序執行。因為,主線程main總會等調用join方法的那個線程執行完之後,才會往下執行。

4) yield

Thread.yield 方法會使當前線程放棄CPU時間片,把執行機會讓給相同或更高優先順序的線程(yield英文意思就是屈服,放棄的意思嘛,可以理解為當前線程暫時屈服於別人了)。

註意,此時當前線程不會阻塞,只是進入了就緒狀態,隨時可以再次獲得CPU時間片,從而進入運行狀態。也就是說,其實yield方法,並不能保證,其它相同或更高優先順序的線程一定會獲得執行權,也有可能,再次被當前線程拿到執行權。

yield方法和sleep方法一樣,也是不釋放鎖資源的。可以通過代碼來驗證這一點:

public class TestYield {
    public static void main(String[] args) {
        YieldThread yieldThread = new YieldThread();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(yieldThread);
            t.start();
        }
    }
}

class YieldThread implements Runnable {

    private int count = 0;

    @Override
    public synchronized void run() {
        for (int i = 0; i < 10; i++) {
            count ++;
            if(count == 1){
                Thread.yield();
                System.out.println("線程:"+Thread.currentThread().getName() + "讓步");
            }
            System.out.println("線程:"+Thread.currentThread().getName() + ",count:"+count);
        }
    }
}

結果:

會看到,線程讓步之後,並不會釋放鎖。因此,其它線程也沒機會獲得鎖,只能把當前線程執行完之後,才會釋放。(對於這一點,其實我是有疑問的。既然yield不釋放鎖,那為什麼還要放棄執行權呢。就算放棄了執行權,別的線程也無法獲得鎖啊。)

所以,我的理解,yield一般用於不存在鎖競爭的多線程環境中。如果當前線程執行的任務時間可能比較長,就可以選擇用yield方法,暫時讓出CPU執行權。讓其它線程也有機會執行任務,而不至於讓CPU資源一直消耗在當前線程。

5)suspend、resume

suspend 會使線程掛起,並且不會自動恢復,只有調用 resume 方法才能使線程進入就緒狀態。註意,這兩個方法由於有可能導致死鎖,已經被廢棄。


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

-Advertisement-
Play Games
更多相關文章
  • <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv ...
  • 設計模式是前人經驗的總結,教大家如何寫出可擴展、可讀、可維護的高質量代碼。設計模式與日常工作中的編碼有直接的關係,直接影響到開發人員的開發能力。 ...
  • 什麼是高階函數:一個函數可以作為參數傳給另外一個函數(一個函數可以用來接收另一個函數作為參數),或者一個函數的返回值為另外一個函數(若返回值為該函數本身,則為遞歸),滿足其一則為高階函數。函數的形參位置必須接受一個函數對象。 代碼理解高階函數的含義: 1 '''函數當做參數被傳遞到另個函數是什麼樣的 ...
  • 一、cookie 1.requests可以自動處理cookie信息 import requests rsp = requests.get("http://www.baidu.com") #如果對方伺服器給傳送過來cookie信息,則可以同通過反饋的cookie屬性得到 #返回一個cookiejar的 ...
  • 不斷的對於某操作重覆調用執行稱為遞歸調用,遞歸函數執行的這個過程中只有進棧(開闢空間),沒有出棧,直到最後一次調用完畢了,才逐個出棧,所以遞歸函數在執行的時候非常的占用記憶體資源;如果執行的次數過多了,會產生記憶體溢出的現象;所以一定要控制遞歸的層數,當符合某一條件時要終止遞歸調用,幾乎所有的遞歸都能用 ...
  • 基於SSM開發倉庫庫存管理系統開發環境: Windows操作系統開發工具: MyEclipse+Jdk+Tomcat+MySql資料庫 源碼及原文鏈接:https://javadao.xyz/forum.php?mod=viewthread&tid=71 運行效果圖 ...
  • 基於JSP+Servlet開發旅游(景點賓館)系統(前臺+後臺): 開發環境: Windows操作系統開發工具: MyEclipse+Jdk+Tomcat+MYSQL資料庫運行效果圖 源碼及原文鏈接:https://javadao.xyz/forum.php?mod=viewthread&tid=6 ...
  • 匿名(lambda)函數: 作用:創始一個匿名函數對象,同 def 類似,但不提供函數名,只是一個表達式,lambda比函數簡單且可以隨時創建和銷毀,有利於減少程式的偶合度。lambda的主體是一個表達式,而不是一個代碼塊。僅僅能在lambda表達式中封裝有限的邏輯進去。lambda 函數擁有自己的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...