一文讀懂LockSupport

来源:https://www.cnblogs.com/star95/archive/2023/08/18/17640946.html
-Advertisement-
Play Games

閱讀本文前,需要儲備的知識點如下,點擊鏈接直接跳轉。 [java線程詳解](https://www.cnblogs.com/star95/p/17583193.html) [Java不能操作記憶體?Unsafe瞭解一下](https://www.cnblogs.com/star95/p/1761943 ...


閱讀本文前,需要儲備的知識點如下,點擊鏈接直接跳轉。
java線程詳解
Java不能操作記憶體?Unsafe瞭解一下

LockSupport介紹

搞java開發的基本都知道J.U.C併發包(即java.util.concurrent包),所有併發相關的類基本都來自於這個包下,這個包是JDK1.5以後由祖師爺Doug Lea寫的,LockSupport也是在這時誕生的,在JDK1.6又加了些操作方法。
其實LockSupport的這些靜態方法基本都是調用Unsafe類的方法,所以建議大家看看文章開頭的Unsafe那篇文章。
首先我們來看看LockSupport類開頭的一段註釋。

/**
 * Basic thread blocking primitives for creating locks and other
 * synchronization classes.
 *
 * <p>This class associates, with each thread that uses it, a permit
 * (in the sense of the {@link java.util.concurrent.Semaphore
 * Semaphore} class). A call to {@code park} will return immediately
 * if the permit is available, consuming it in the process; otherwise
 * it <em>may</em> block.  A call to {@code unpark} makes the permit
 * available, if it was not already available. (Unlike with Semaphores
 * though, permits do not accumulate. There is at most one.)

大概的意思就是說,LockSupport這個類用於創建鎖和其他同步類的基本線程阻塞原語。這個類與使用它的每個線程關聯一個許可證,這個許可證數量不會累積。最多只有一個即permit要麼是0要麼是1,如果調用park方法,permit=1時則當前線程繼續執行,否則沒有獲取到許可證,阻塞當前線程;調用unpark方法會釋放一個許可,把permit置為1,連續多次調用unpark只會把許可證置為1一次,被阻塞的線程獲取許可後繼續執行。
額,可能剛開始接觸這個類的童鞋有點懵逼,不過沒關係,下麵我為大家準備了飲料小菜花生米,諸位搬好小板凳,靜靜的聽我吹牛逼吧,哈哈。

API

LockSupport類只有幾個靜態方法,構造方法是私有的,所以使用的過程中就調用它的這幾個靜態方法就夠了。

  • 單純的設置和獲取阻塞對象。
// 給線程t設置阻塞對象為arg,以便出問題時排查阻塞對象,這個方法為私有方法,其他park的靜態方法會調用這個方法設置blocker
private static void setBlocker(Thread t, Object arg)
// 獲取線程t的阻塞對象,一般用於排查問題
public static Object getBlocker(Thread t)
  • 單純的阻塞和給線程釋放許可
// 阻塞當前線程,如果已經獲取到許可則不阻塞繼續執行,這個阻塞可以響應中斷
public static void park()
// 釋放線程thread的許可,使得thread線程從park處繼續向後執行,如果threa為null不做任何操作
public static void unpark(Thread thread)
  • 只帶時間的阻塞
// 阻塞線程,設置了等待超時時間,單位是納秒,是相對時間,nanos<=0不會阻塞,相當於沒有任何操作;nanos>0時,如果等待時間超過nanos納秒還沒有獲取到許可,那麼線程自動恢復執行
public static void parkNanos(long nanos)
// 這裡的deadline單位是毫秒,而且是絕對時間,調用後會阻塞到指定的絕對時間如果還沒有獲取到許可則自動恢復執行
public static void parkUntil(long deadline)
  • 同時帶阻塞對象和時間的阻塞
// 預設的許可permit=0,阻塞當前線程,並設置阻塞對象為blocker其實就是調用setBlocker這個私有方法。如果當前線程的permit=1了那麼再調park是不會阻塞的,因為可以獲取到許可繼續執行。當前線程獲取到許可後會清除blocker為null
public static void park(Object blocker)
// 作用同park(Object blocker)方法,唯一的區別就是設置了等待超時時間,單位是納秒,是相對時間,nanos<=0不會阻塞,相當於沒有任何操作;nanos>0時,如果等待時間超過nanos納秒還沒有獲取到許可,那麼線程自動恢復執行,例如nanos=1000*1000*1000,這個相當於1秒,等到1秒後如果還沒有獲取到許可醒則自動恢復
public static void parkNanos(Object blocker, long nanos)
// 作用同parkNanos(Object blocker, long nanos),設置阻塞對象blocker,但是這裡的deadline單位是毫秒,而且是絕對時間,調用了parkUntil後會阻塞到指定的絕對時間如果還沒有獲取到許可則自動恢復執行
public static void parkUntil(Object blocker, long deadline)
  • 其他(別問,問我也不知道)
// 返回偽隨機初始化或更新的輔助種子。由於包訪問限制,從ThreadLocalRandom複製。PS:這是百度翻譯的,平時用得少,我也沒用過,暫且先放這裡吧,用到了再細講
static final int nextSecondarySeed()

關於LockSupport的park相關方法阻塞,有以下三種方法可獲取到許可並繼續向後執行。

  1. 主動調用unpark(Thread thread)方法,使得線程獲得許可繼續執行。
  2. 中斷該線程即調用interrupt()方法,調用後線程不會拋出異常,直接從park的地方恢復過來繼續執行
  3. 無原因的虛擬的返回,這種情況目前沒有遇到過,不過在java.util.concurrent.locks.LockSupport.park()的註釋里會有這種情況

使用案例

  • 基礎的阻塞和釋放許可
public static void test1() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 調用park()方法會一直阻塞直到獲得permit或者被中斷
            LockSupport.park();
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " get permit");
        }
    }, "t1");
    t.start();
    Thread.sleep(2000);
    // 使得被阻塞的線程繼續執行有三種方法
    // 1、主動調用unpark(Thread thread)方法,使得線程獲得許可繼續執行
    LockSupport.unpark(t);
    // 2、中斷該線程即調用interrupt()方法,調用後線程不會拋出異常,直接從park的地方恢復過來繼續執行
    // t.interrupt();
    // 3、無原因的虛擬的返回,這種情況目前沒有遇到過,不過在java.util.concurrent.locks.LockSupport.park()的註釋里會有這種情況
}

輸出如下:

2020-05-19 20:25:16:t1 is running...
2020-05-19 20:25:18:t1 get permit
  • 設置和獲取阻塞對象
public static void test2() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 設置線程的阻塞對象為一個字元串
            LockSupport.park("i am blocker");
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " get permit");
        }
    }, "t1");
    t.start();
    Thread.sleep(2000);
    // 獲取t線程的阻塞對象,如果沒有設置線程t的阻塞對象,則獲取到的blocker是null
    Object blocker = LockSupport.getBlocker(t);
    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":" + Thread.currentThread().getName()
        + " get block class type:" + blocker.getClass());
    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":" + Thread.currentThread().getName()
        + " get block value toString:" + blocker);
    LockSupport.unpark(t);
}

輸出:

2020-05-19 20:32:00:t1 is running...
2020-05-19 20:32:02:main get block class type:class java.lang.String
2020-05-19 20:32:02:main get block value toString:i am blocker
2020-05-19 20:32:02:t1 get permit
  • 帶相對和絕對時間的阻塞
public static void test3() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 設置線程的阻塞對象為一個字元串,並且阻塞3s,這裡是相對時間,如有沒有被unpark或者線程中斷,3s後自動恢復執行
            LockSupport.parkNanos("block1", TimeUnit.SECONDS.toNanos(3));
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is running...");
            // 設置線程的阻塞對象為一個字元串,並且阻塞5s,這裡使用的是絕對時間,只到當前時間+5s轉換為毫秒,如有沒有被unpark或者線程中斷,絕對時間到後自動恢復執行
            LockSupport.parkUntil("block2", System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(5));
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "t2");
    t2.start();
}

輸出結果:

2020-05-20 08:35:07:t2 is running...
2020-05-20 08:35:07:t1 is running...
2020-05-20 08:35:10:t1 continue...
2020-05-20 08:35:12:t2 continue...
關於park阻塞的方法,針對於阻塞時間總結一下,有三種使用情況情況。
- 無限期阻塞,不帶任何時間相關的參數,這種底層調用的是UNSAFE.park(false, 0L)。
- 相對時間阻塞,調用的parkNanos相關方法,這裡的時間參數是一個相對時間,單位是納秒,這種底層調用的是UNSAFE.park(false, nanos),表示經過nanos納秒後如果還未獲取到許可則自動恢復執行。
- 絕對時間阻塞,調用的parkUntil相關方法,這裡的時間參數是一個絕對時間,單位是毫秒,這種底層調用的是UNSAFE.park(true, deadline),表示把當前時間換算成毫秒,如果值等於deadline毫秒後未獲取到許可則自動恢復執行。

與對象鎖比較

LockSupport與對象鎖主要區別如下:

  1. 關註維度不同
    LockSupport是針對於線程級別的,而對象鎖是synchronized關鍵字配合object對象的notify()、notifyAll()和wait()方法使用的,這種是針對於對象級別的。兩者阻塞方式不同,我們看個慄子吧。
public static void test4() throws Exception {
    Thread t1 = new Thread(new Runnable() {
        @Override
        public void run() {
            LockSupport.park();
        }
    }, "t1");
    t1.start();
    Thread t2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                Object obj = new Object();
                synchronized (obj) {
                    obj.wait();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "t2");
    t2.start();
}

上面這段代碼創建了兩個線程,t1使用LockSupport.park()阻塞,t2使用obj.wait()阻塞,調用這個方法執行後,我們看看jvm的線程信息。

  • 先用jps找到對應的進程

  • 使用jstack查看線程信息

從這個dump的線程堆棧信息我們可以看出,t1和t2線程都處於WATING狀態,但是t1是阻塞在了Unsafe.park方法上,parking狀態,等待獲取許可,t2是阻塞在Object.wait方法上,在等待一個object monitor即對象鎖。
2. 喚醒方式不同
LockSupport是喚醒指定的線程,而notify()或者notifyAll()無法指定要喚醒的線程,只是表明對象上的鎖釋放了,讓其他等待該鎖的線程繼續競爭鎖,至於哪個線程先獲取到鎖是隨機的,只是將獲取到鎖的線程由阻塞等待狀態變成就緒狀態,等待操作系統的調度才能繼續執行。
3. 使用方式不同
LockSupport的park阻塞方式是在當前線程中執行並阻塞當前線程,但是喚醒unpark方法是在其他線程中執行的,並且喚醒後被park阻塞的方法能立即繼續執行。但是notify或者notifyAll方法雖然調用後起到了通知釋放對象鎖的作用,但是他必須退出synchronized後才生效,下麵我們分別看兩個慄子。
LockSupport的park和unpark

public static void test5() throws Exception {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Thread lockSupportThread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " is go parking...");
            LockSupport.park();
            System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                + Thread.currentThread().getName() + " continue...");
        }
    }, "lockSupportThread1");
    // 讓lockSupportThread1線程先執行起來
    lockSupportThread1.start();
    Thread lockSupportThread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                    + Thread.currentThread().getName() + " is running...");
                // 讓當前線程休眠1s
                Thread.sleep(1000);
                // unpark線程lockSupportThread1
                LockSupport.unpark(lockSupportThread1);
                // 讓當前線程休眠3s
                Thread.sleep(3000);
                System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                    + Thread.currentThread().getName() + " over...");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread2");
    lockSupportThread2.start();
}

輸出:

2020-05-21 12:11:27:lockSupportThread2 is running...
2020-05-21 12:11:27:lockSupportThread1 is go parking...
2020-05-21 12:11:28:lockSupportThread1 continue...
2020-05-21 12:11:31:lockSupportThread2 over...

這裡我們看到lockSupportThread2線程調用LockSupport.unpark後,雖然有休眠,但是lockSupportThread1線程還是立即執行了,說明LockSupport.unpark是立即釋放線程許可。
接下來我們看下Object的wait()和notifyAll()。

public static void test6() {
    DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    Object object = new Object();
    Thread lockSupportThread3 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                synchronized (object) {
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " is running...");
                    // 釋放object鎖讓其他線程可以獲得,當前線程阻塞
                    object.wait();
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " over...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread3");
    lockSupportThread3.start();
    Thread lockSupportThread4 = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 讓當前線程休眠2s,確保lockSupportThread3先獲取到object鎖
                Thread.sleep(2000);
                synchronized (object) {
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " is running...");
                    // 讓當前線程休眠1s
                    Thread.sleep(1000);
                    // 喚醒等待在object鎖上的線程
                    object.notifyAll();
                    // 讓當前線程休眠3s
                    Thread.sleep(3000);
                    System.out.println(LocalDateTime.now().format(dateTimeFormatter) + ":"
                        + Thread.currentThread().getName() + " over...");
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }, "lockSupportThread4");
    lockSupportThread4.start();
}

輸出結果:

2020-05-21 12:16:51:lockSupportThread3 is running...
2020-05-21 12:16:53:lockSupportThread4 is running...
2020-05-21 12:16:57:lockSupportThread4 over...
2020-05-21 12:16:57:lockSupportThread3 over...

lockSupportThread4先休眠了2s確保lockSupportThread3先執行並獲取到object對象鎖,然後lockSupportThread3調用了object.wait(),釋放object鎖並線程阻塞等待,然後lockSupportThread4獲取到了object鎖繼續執行,雖然lockSupportThread4在休眠和列印輸出前調用了notifyAll方法,但是依然是lockSupportThread4的同步塊代碼執行完成後lockSupportThread3才開始執行。

總結

本文中雖然我們只介紹了LockSupport的API方法和使用案例,其實這也是除synchronized結合Object的wait()、notify()、notifyAll()來協調多線程同步的另一種方式。而且在只協調多線程的的情況下LockSupport會顯得更靈活。
另外在jdk的併發包下,有各種鎖,比如ReentrantLockCountDownLatchCyclicBarrier等,只要往底層看下源碼,可以發現他們都使用了AbstractQueuedSynchronizer(簡稱AQS,抽象隊列同步器,後續文章會專門介紹),而AbstractQueuedSynchronizer里的線程阻塞和喚醒正是使用的就是LockSupport,所以想要搞懂原理,就得把這些一一梳理清楚,最後自然而然就明白了。

說到這裡,讓我突然想起張三豐教張無忌學太極時的那一段對話。

張三豐:“無忌,我教你的還記得多少?”
張無忌:“回太師傅,我只記得一大半”
張三豐:“ 那,現在呢?”
張無忌:“已經剩下一小半了”
張三豐:“那,現在呢?”
張無忌:“我已經把所有的全忘記了!”
張三豐:“好,忘了好,剛纔教你的都是錯的,重新來吧...”
張無忌:......

emmmmm,好像走錯片場了,那就江湖再見吧。。。


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

-Advertisement-
Play Games
更多相關文章
  • ##一、前言 **工廠模式常見的詞:簡單工廠、工廠方法、抽象工廠。簡單工廠不屬於23種經典設計模式,但通常將它作為學習其他工廠模式的基礎。** ##二、簡單工廠 ###1、定義 **定義一個工廠類,它可以根據參數的不同返回不同類型的實例,被創建的實例通常都具有共同的父類。由於簡單的工廠模式中用於創建 ...
  • 背景: PyAV是一個用於音頻和視頻處理的Python庫,它提供了一個簡單而強大的介面,用於解碼、編碼、處理和分析各種音頻和視頻格式。PyAV基於FFmpeg多媒體框架,它本質上是FFmpeg 的Python綁定,因此可以利用FFmpeg的功能來處理各種多媒體任務。 基本概念: 編解碼器(Codec ...
  • 本文通過設計演講比賽流程管理系統,全面介紹了使用C++面向對象編程思想開發項目應用的過程,涵蓋了需求分析、系統架構設計、類的提取,以及採用多種STL容器配合演算法的具體實現。文中詳細展示了構建選手類、管理類,設計菜單界面與用戶交互、實現兩輪比賽流程的抽簽、評分模塊,並能夠完成記錄文件的讀寫與管理 ...
  • **原文鏈接:** [Go 語言中排序的 3 種方法](https://mp.weixin.qq.com/s/RyVc_JZJi_pTT_51mEHYiw) 在寫代碼過程中,排序是經常會遇到的需求,本文會介紹三種常用的方法。 廢話不多說,下麵正文開始。 ## 使用標準庫 根據場景直接使用標準庫中的方 ...
  • 本文通過簡單的示例代碼和說明,讓讀者能夠瞭解微服務如何集成redis 之前的教程 https://www.cnblogs.com/leafstar/p/17638933.html 由於redis的安裝網上教程很多,再次不再贅述,現在預設你已經安裝好了redis 1.在需要redis的微服務下麵添加下 ...
  • 配置Redis哨兵集群時日誌顯示+sdown slave的問題 一、配置及其環境描述(問題產生的原因是因為Redis複製中主節點對從節點的ip配置錯誤,從而導致哨兵無法識別從節點,進而無法進行故障轉移) 1.操作系統:Linux 虛擬機:VMware Workstation 16 Pro 、WSL ...
  • ![image](https://img2023.cnblogs.com/blog/2609621/202308/2609621-20230818091044358-21979402.png) ```html 點擊觸發頁面彈窗 ``` ```js alert('努力,奮鬥') ``` ![image ...
  • 翻了一下之前剛入職時候的學習筆記,發現之前在熟悉業務代碼的時候曾經專門學習並整理過過設計模式中的責任鏈模式,之前只是對其簡單瞭解過常用的設計模式有哪些,並未結合實例和源碼深入對其探究,利用熟悉代碼契機進行系統學習並整理文檔如下。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...