我看誰還不懂多線程之間的通信+基礎入門+實戰教程+詳細介紹+附源碼

来源:https://www.cnblogs.com/malongfeistudy/archive/2022/11/05/16860109.html
-Advertisement-
Play Games

一、多線程之間的通信(Java版本) 1、多線程概念介紹 多線程概念 在我們的程式層面來說,多線程通常是在每個進程中執行的,相應的附和我們常說的線程與進程之間的關係。線程與進程的關係:線程可以說是進程的兒子,一個進程可以有多個線程。但是對於線程來說,只屬於一個進程。再說說進程,每個進程的有一個主線程 ...


一、多線程之間的通信(Java版本)

1、多線程概念介紹

多線程概念

  • 在我們的程式層面來說,多線程通常是在每個進程中執行的,相應的附和我們常說的線程與進程之間的關係。線程與進程的關係:線程可以說是進程的兒子,一個進程可以有多個線程。但是對於線程來說,只屬於一個進程。再說說進程,每個進程的有一個主線程作為入口,也有自己的唯一標識PID,它的PID也就是這個主線程的線程ID

  • 對於我們的電腦硬體來說,線程是進程中的一部分,也是進程的的實際運作單位,它也是操作系統中的最小運算調度單位。多線程可以提高CPU的處理速度。當然除了單核CPU,因為單核心CPU同一時間只能處理一個線程。在多線程環境下,對於單核CP來說,並不能提高響應速度,而且還會因為頻繁切換線程上下文導致性能降低。多核心CPU具有同時並行執行線程的能力,因此我們需要註意使用環境。線程數超出核心數時也會引起線程切換,並且操作系統對我們線程切換是隨機的。

2、線程之間如何通信

引入

  • 對於我們Java語言來說,多線程編程也是它的特性之一。我們需要利用多線程操作同一共用資源,從而實現一些特殊任務。上面說了,多線程在進行切換時CPU隨機調度的,假如我們直接運行多個線程操作共用資源的話,勢必會引起一些不可控錯誤因素。
  • 接下來,我們就需要讓這些不可控變為可控 !這個時候就引出了本文的重點線程通信。線程通信就是為瞭解決多線程對同一共用變數的爭奪

Java 線程通信的方式

  • 共用記憶體機制
    • 比如說Java的volatile關鍵字就是基於記憶體屏障解決變數的可見性,從而實現其他線程訪問共用變數都是必須從主存中獲取(對應其他線程對變數的更新也得及時的刷新到主存)。
    • synchronized 關鍵字基於對象鎖這種方式實現線程互斥,可以通知對方有其他的線程正在執行這部分代碼。
  • 消息傳遞模式
    • wait() 和 notify()/notifyAll() 等待通知方式實現線程的阻塞就緒狀態之間的轉換。
    • park、unpark
    • join() 阻塞【底層也是依賴wait實現】。
    • interrupt()打斷阻塞狀態。
    • 管道輸入/輸出。

3、線程通信方法詳細介紹

主要介紹wait/notify,也有ReentrantLock的Condition條件變數的await/signal,LockSupport的park/unpark方法,也能實現線程之間的通信。主要是阻塞/喚醒通信模式。

首先說明這種方法一般都是作用於調用方法的所線上程。比如在主線程執行wait方法,就是將主線程阻塞了。

wait/notify機制

  • wait()、notify方法在Java中是Object提供給我們的。又因為所有的類都預設隱式繼承了Object類,進而我們的每一個對象都具有wait和notify。
    • wait方法含義:一個線程一旦調用了任意對象obj.wait()方法,它就釋放了所持有的監視器對象(obj)上的鎖,並轉為非運行狀態(阻塞)。
    • notify方法含義:一個線程若執行obj.notify方法,則隨機喚醒obj對象上監視器(操作系統也稱為管程)monitor的阻塞隊列waitset中一個線程。
    • wait和notify方法的使用同時必須配合synchronized關鍵字使用。同時也需要成對出現。就是說wait和notify必須得在同步代碼塊內部使用,大致原因就是需要保證同時只有一個線程可以去執行wait,使該線程阻塞。

await/signal

  • 要想使用await/signal首先是需要借用Condition條件變數,要想獲取Condition條件變數,就必須通過ReentrantLock鎖獲取。
  • ReentrantLock和Synchronized類似,都是可重入鎖,並且大多都是當做重量級鎖使用。
    • 區別:ReentrantLock是API層面實現的,我們可以根據自己隨意調用定製,但是Synchronized是JVM底層實現,我們無需關心他上鎖解鎖的流程。
  • await/signal使用時需要配合ReentrantLock鎖對象的lock和unlock方法加鎖解鎖。就像wait/notify在synchronized在同步代碼塊中使用一樣。他們都需要保證當前線程是唯一執行這段邏輯的線程。防止出現多線程造成的線程安全問題。

park/unpark

二、線程通信過程中需要註意的問題

1、喚醒丟失

如果一個線程先於被通知線程調用wait()前調用了notify(),等待的線程將錯過這個信號。

  • 喚醒丟失主要是在我們使用wait 和 notify的過程中的時序問題。比如說我們線程二在執行某個對象notify的時候,線程一還沒有執行該對象的wait方法。那麼這次的喚醒就會丟失,我們就不能讓線程二得notify方法起作用,自然而然線程一就不會被喚醒。
  • 舉個例子吧,這就好比我們平常在宿舍每天都會有叫醒服務,但是這次 因為一些原因(通宵···)我一整晚都沒有睡覺,而且當第二天早上的叫醒服務來的 時候也是醒著的。那麼叫醒服務就會以為你已經醒來了,就會視而不見。沒想到吧,叫醒服務剛走我就躺下來睡著了,所以我錯過了這次叫醒服務。就能好好的睡覺了。這看起來沒有什麼大問題,但是你仔細想想若是每個睡著的人都需要被叫醒服務才能醒過來,外加上只有一次叫醒服務的機會。那麼你就可以沉睡萬年了,開心不。
  • 哈哈哈···
  • 這在程式中也是一樣 的,如果錯過notify那麼就會一直wait。
    • 所以我們必須預防這種問題,比如說每隔一段時間去喚醒,也就是隔兩分鐘就去叫醒睡著的人。但是這種缺點就是太累了,對於程式來說是消耗性能和記憶體。實現也簡單就是寫入while迴圈體中,不停地嘗試即可。
    • 我們也可以使用一個標誌位完美的實現。初始化設置flag=FALSE表示還沒wait,在wait之前將設置flag=TRUE,在notify之後設置flag=FALSE。每次notify喚醒之前都判斷flag=true是否已經wait,在wait中判斷flag=false是否已經notify。

核心代碼演示

  • 首先使用線程池創建線程一使自己進入阻塞態,然後再調用LOCK1的notify方法喚醒線程一
	    // 線程一使用LOCK1對象調用wait方法阻塞自己
        executor.execute(new ThreadTest("線程一",LOCK1,LOCK2));

        synchronized (LOCK1) {
            System.out.println("main執行notify方法讓線程一醒過來");
            LOCK1.notify();
        }
  • 但是他很有可能醒不來,因為主線程調用LOCK1對象的notify方法,可能主線程已經執行完了,上麵線程還沒創建完成,也就是沒有進入wait狀態。就醒不來了。

  • 解決方式:使用信號量標誌進行判斷是否已經進入wait

            synchronized (LOCK1) {
                while (true) {
                    if (FLAG.getFlag()) {
                        System.out.println("main馬上執行notify方法讓線程一醒過來" + "flag = " + FLAG.getFlag());
                        LOCK1.notify();
                        // 將標誌位變為FALSE
                        FLAG.setFlag(Constants.WaitOrNoWait.NO_WAIT.getFlag());
                        System.out.println("main執行notify方法完畢" + "flag = " + FLAG.getFlag());
                        break;
                    }
                }
            }
    

2、假喚醒

由於莫名其妙的原因,線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。

  • 其實在上面的代碼中已經解決了假喚醒的問題,因為我們只需要不斷去嘗試獲取標誌位信息即可。

3、多線程喚醒

  • 多個線程執行時,防止notifyAll全部喚醒之後就結束運行,我們的需求是只能喚醒一個線程,當其他線程被喚醒之後需要重新判斷標誌位是否為FALSE,也就是需要判斷是否有其他線程執行了喚醒操作,因為一次只能叫醒一個人,需要排隊,他們就可以繼續自旋判斷。
		synchronized (waitName) {
            while (!flag.getFlag()) {
                try {
                    // 將標誌位設置為TRUE
                    flag.setFlag(Constants.WaitOrNoWait.WAIT.getFlag());
                    System.out.println("name;"+name+" 我睡著了進入阻塞狀態" + "flag = " + flag.getFlag());
                    waitName.wait();
                    System.out.println("name;"+name+" 我醒來了" + "flag = " + flag.getFlag());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
  • 大家如果使用的是new Thread()方式創建線程的話,要想保證安全的話還可以給該標誌位加上volatile關鍵字,可以時刻保證該標誌位的可見性。
  • 我這裡使用的標誌位是使用傳遞引用的方式,使用同一個對象,將標誌位定義為該對象中的屬性,然後再結合枚舉類進行設置標誌位的值。因為我使用線程池創建對象,並且自定義線程類,這裡是無法設置全局變數,傳遞給線程類。包裝類也不行哦。(感興趣可以親自試一下)
  • 大體代碼結構如下所示:
	private final static Object LOCK1 = new Object();
    private final static Object LOCK2 = new Object();
    private final  static Constants.WaitStatus FLAG = new Constants.WaitStatus(false);
    public static void main(String[] args) throws InterruptedException {
        ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5, 1, TimeUnit.DAYS, new ArrayBlockingQueue<>(4), new ThreadPoolExecutor.AbortPolicy());
        executor.execute(new ThreadTest("線程一",LOCK1,LOCK2, FLAG));
        // ···喚醒
    }

class ThreadTest implements Runnable { //阻塞··· }

完整代碼可以看這[Gitee倉庫完整代碼][https://gitee.com/malongfeistudy/javabase/tree/master/Java多線程_Study/src/main/java/com/mlf/thread/demo_wait_notify]

三、線程通信實戰

前置知識:線程池的使用方法

  • 首先複習一下創建線程的幾種方式和其的優缺點:

    • 通過new Thread()
    • 繼承Thread():和new Thread沒啥區別,就是耦合度低了
      • 定義線程類繼承Thread類並且重寫run方法即可。
      • 優點是簡潔方便
      • 缺點是占用了該類的單繼承位置,無法繼承其他父類
    • 實現Runnable介面
    • 實現Callable介面
      • 和實現Runnable介面類似
      • 優點:
        • 實現介面,不占用繼承的位置;
        • 耦合度降低,並且可定化程度提高。各個模塊之間的調用關係更加清晰
      • 缺點:
        • 實現起來稍微麻煩
  • 使用線程池的步驟

    • 線程池初始化方式:
      • 使用Executor初始化線程池
        • 優點:方便快捷,適用於自己測試時使用
        • 缺點:在實際開發中無法判斷細節
      • new ThreadPoolExecutor()構造器創建(本文使用方式)
        • 優點:可以清晰地定製出適合自己的線程池,不會造成資源浪費
        • 缺點:麻煩
  • 在主線程自定義線程池使用實例,這裡需要根據實際情況定義鎖對象,因為我們需要使用這些鎖對象控制多線程之間的運行順序以及線程之間的通信。在Java中每個對象都會在初始化的時候擁有一個監視器,我們需要利用好他進行併發編程。這種創建線程池的方法也是阿裡巴巴推薦的方式,想想以阿裡的體量多年總結出來的總沒有錯,大家還是提前約束自己的編碼習慣等。安裝一個阿裡代碼規範的插件對自己的程式員道路是比較nice的。

    /**
     * 每個使用對應唯一的對象作為監視器對象鎖。
     */
    public static final Object A_O = new Object();
    public static final Object B_O = new Object(); 
        /** 參數:
         * int corePoolSize,                     核心線程數
         * int maximumPoolSize,                  最大線程數
         * long keepAliveTime,                   救急存活時間
         * TimeUnit unit,                        單時間位
         * BlockingQueue<Runnable> workQueue,    阻塞隊列
         * RejectedExecutionHandler handler      拒絕策略
         **/
        // 使用阿裡巴巴推薦的創建線程池的方式
        // 通過ThreadPoolExecutor構造函數自定義參數創建
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                3,
                5,
                1,
                TimeUnit.DAYS,
                new ArrayBlockingQueue<>(2),
                new ThreadPoolExecutor.AbortPolicy());
  • 接下來需要自定義線程類,我們可以自定義線程類,並且在該線程類中定義自己需要的共用資源(鎖對象,屬性等),在run方法中寫盡自己的線程運行邏輯即可。
class ThreadDiy implements Runnable {

    private final String name;

    /**
     * 阻塞鎖對象  等待標記
     **/
    private final Object waitFor;

    /**
     * 執行鎖對象  下一個標記
     **/
    private final Object next;

    public AlternateThread(String name, Object waitFor, Object next) {
    }

    @Override
    public void run() {
        // 線程的代碼邏輯···
    }

}

1、控制兩個線程之間的執行順序

題目:現在有兩個線程,不論線程的啟動順序,我需要指定線程一先執行,然後線程二再執行。

  • 初始化兩個對象鎖作為線程監視器。

        private final static Object ONE_LOCK = new Object();
        private final static Object TWO_LOCK = new Object();
    
  • 接下來初始化線程池,上面有具體的介紹,在這就不多說了

  • 使用線程池去執行我們的兩個線程,在這裡我們需要分析的是

        // 使用線程池創建線程
        executor.execute(new DiyThread(1, ONE_LOCK, TWO_LOCK));
        executor.execute(new DiyThread(2, TWO_LOCK, ONE_LOCK));

        synchronized (ONE_LOCK) {
            ONE_LOCK.notify();
        }

創建線程類

  • 我們使用繼承Runnable的方式去創建線程對象,需要在這個類中實現每個線程執行的邏輯,我們根據題目可以得出,我們要控制每個線程的執行順序,怎麼辦?那麼就要實現所有線程之間的通信,通信方式採用wait-notify的方式即可。我們使用wait-notify的時候必須結合synchronized,那麼就需要控制兩個對象鎖。因為我們不光是控制自己,還有另一個線程。

  • 我們再分析一下題意,首先需要指定先後執行的順序,那麼就需要實現兩個線程之間的通信。其次呢,我們得控制兩個線程,那麼就需要兩個監視器去監視這兩個線程。

  • 我們定義這兩個監視器對象為own和other。然後再新增一個屬性threadId來標識自己。

        private final int threadId;
        private final Object own;
        private final Object other;
    
  • 接下來就是編寫Run方法了

  • 每個線程首先需要阻塞自己,等待喚醒。然後喚醒之後,再去喚醒另外一個線程。這樣就實現了自定義順序。至於先喚醒哪個線程,交給我們的主線程去完成。

  • 這裡需要註意的是,如果我們只是單純地執行了多個線程對象,但是主線程沒有主動去喚醒其中一個,這樣就會形成類似於死鎖的迴圈等待。你需要我喚醒,我需要你喚醒。這個時候需要主線程去插手喚醒其中的任意一個線程。

    • 第一步阻塞自己own

              synchronized (own) {
                  try {
                      own.wait();
                      System.out.println(num);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
              }
      
    • 第二步喚醒other

              synchronized (other) {
                  other.notify();
              }
      

2、多線程交替列印輸出

題目需求:現在需要使用三個線程輪流列印輸出。說白了也就是多線程輪流執行罷了,和問題一控制兩個線程列印順序沒什麼區別

  • 還是老步驟,首先需要定義線程類,我們需要控制當前線程和下一個線程即可。我們這裡需要兩個對象,一個是阻塞鎖對象用來阻塞當前線程。另一個是喚醒鎖對象,用來喚醒下一個對象。
    /**
     * 阻塞鎖對象  等待標記
     **/
    private final Object waitFor;
    /**
     * 喚醒鎖對象  下一個標記
     **/
    private final Object next;
  • run方法的邏輯和上面的基本一樣。 一個線程一旦調用了任意對象的wait()方法,它就釋放了所持有的監視器對象上的鎖,並轉為非運行狀態。

  • 每個線程首先會調用 waitFor對象的 wait()方法,隨後該線程進入阻塞狀態,等待其他線程執行自己引用的該 waitFor對象的 notify()方法即可。

    		while (true) {
                synchronized (waitFor) {
                    try {
                        waitFor.wait();
                        System.out.println(name + " 開始執行");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                synchronized (next) {
                    next.notify();
                }
            }
    
  • 主線程需要初始化線程池、執行三個線程,並且最後需要打破僵局,因為此時每個線程都是阻塞狀態,他們沒法阻塞/喚醒迴圈下去。

            synchronized (A_O) {
                A_O.notify();
            }
    
  • 模擬執行流程

/**
 * 模擬執行流程
 * 列印名(name)    等待標記(waitFor)   下一個標記(next)
 *      1                 A                  B
 *      2                 B                  C
 *      3                 C                  A
 * 
 * 像不像Spring的迴圈依賴:確實很像,Spring中的迴圈依賴就是 BeanA 依賴 BeanB,BeanB 依賴 BeanA;
 * 他們實例化過程中都需要先屬性註入對方的實例,倘若剛開始的時候都沒有實例化,初始化就會死等。類似於死鎖。
 **/

3、多線程順序列印同一個自增變數

使用多線程輪流列印 01234····

  • 思路:使用自增原子變數AtomicInteger和多線程配合列印。

具體代碼請移步到Gitee倉庫:[順序列印自增變數][https://gitee.com/malongfeistudy/javabase/blob/master/Java多線程_Study/src/main/java/com/mlf/thread/print/AddNumberPrint2.java]

條件變數Condition的使用

  • Condition是一個 LOCK 實例出來的,他們獲取的都是一個 LOCK 的鎖,而如果要調用 object的 wait和notify 方法,首先要獲取對應的object的鎖,如果要調用Condition 的await、signal方法,必須先獲取Lock鎖(Lock.lock)。
  • 多線程的初衷就是操作共用資源,然後我們需要保證共用資源同一時刻只能被一個線程所修改。那麼就需要一把鎖來控制這些線程之間互斥條件。這裡使用一個ReentrantLock鎖作為我們的Lock對象。通過同一個 Lock鎖 獲取的每個Condition 就可以作為每個線程自己的阻塞條件和喚醒條件。

如有問題,請留言評論。

作者:小白且菜鳥 出處:https://www.cnblogs.com/malongfeistudy/
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 前後端分離開發,後端需要編寫接⼝說明⽂檔,會耗費⽐較多的時間。 swagger 是⼀個⽤於⽣成伺服器接⼝的規範性⽂檔,並且能夠對接⼝進⾏測試的⼯具。 作用 ⽣成接⼝說明⽂檔 對接⼝進⾏測試 使用步驟 添加依賴 <!--swagger--> <dependency> <groupId>io.sprin ...
  • 原文鏈接:https://www.zhoubotong.site/post/87.html 之所以寫這篇文章,是覺得裡面有些細節如果不註意,很容易出錯或踩坑,網上有很多教程對這塊的描述部分存在錯誤。希望下麵的介紹能給大家帶來幫助。 大家知道當我們需要初始化類中的成員變數時,除了可以直接在構造函數裡面 ...
  • 前言 大家早好、午好、晚好吖~ 問題描述 相信很多剛開始使用pycharm不太熟練的小伙伴, 每天一開機打開pycharm總是卡半天,不知道的還以為是電腦卡了或者啥問題的。 莫慌,其實並不是… 今天我們就來解決一下這個問題 解決方法 大致總結了以下這幾種方法 1、exclude不必要文件 依次打開 ...
  • 本文主要介紹 Logstash 的一些常用輸出插件;相關的環境及軟體信息如下:CentOS 7.9、Logstash 8.2.2。 1、Stdout 輸出插件 Stdout 插件把結果數據輸出到標準輸出。 input { stdin { } } output { stdout { } } 2、Fil ...
  • 2022-11-04 一、元數據 1、元數據的說明: 元數據就是指描述數據的數據,例如:數據有多少列、數據的列名稱等。 2、使用的代碼: 1 ResultSetMetaData metaData = rs.getMetaData(); 2 int columnCount = metaData.get ...
  • 哈嘍,兄弟們, 本文帶大家複習一下Python基礎中的字元串,不知道大家還記得多少內容呢? 字元串 1、字元串就是一系列字元 在python中,用引號括起的都是字元串,其中引號可以是單的,也可以是雙的。例如: “i am not happy” ‘i am not happy’ 這種靈活性能在字元串中 ...
  • 一、Statement對象 Jdbc中的statement對象用於向資料庫發送SQL語句,想完成對資料庫的增刪改查,只需要通過這個對象 向資料庫發送增刪改查語句即可。 Statement對象的executeUpdate方法,用於向資料庫發送增、刪、改的sql語句,executeUpdate執行 完後 ...
  • 函數介紹 函數功能簡單介紹 庫函數介紹 import requests#請求網頁 from lxml import etree#對網頁進行解析 函數功能介紹 函數1 def getdata(url): html=requests.get(url).text # print(html) doc=etr ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...