帶你看看Java的鎖(一)-ReentrantLock

来源:https://www.cnblogs.com/burg-xun/archive/2020/04/05/12638786.html
-Advertisement-
Play Games

前言 AQS一共花了5篇文章,對裡面實現的核心源碼都做了註解 也和大家詳細描述了下,後面的幾篇文字我將和大家聊聊一下AQS的實際使用,主要會聊幾張鎖,第一篇我會和大家聊下ReentrantLock 重入鎖,雖然在講AQS中也穿插了講了一下,但是我還是深入的聊下 ==PS:前面5篇寫在了CSDN裡面 ...


前言

AQS一共花了5篇文章,對裡面實現的核心源碼都做了註解 也和大家詳細描述了下,後面的幾篇文字我將和大家聊聊一下AQS的實際使用,主要會聊幾張鎖,第一篇我會和大家聊下ReentrantLock 重入鎖,雖然在講AQS中也穿插了講了一下,但是我還是深入的聊下

PS:前面5篇寫在了CSDN裡面 懶得轉過來來了,有興趣的移步去看下吧

  1. Java-AQS同步器 源碼解讀1-獨占鎖加鎖
  2. Java-AQS同步器 源碼解讀2-獨占鎖解鎖
  3. Java-AQS同步器 源碼解讀3-共用鎖
  4. Java-AQS同步器 源碼解讀4-條件隊列上
  5. Java-AQS同步器 源碼解讀5-條件隊列下

ReentrantLock簡介

ReentrantLock中文翻譯:重入鎖。那具體重入是什麼意思呢,如果看過前面幾篇文章的人,應該瞭解一下,簡答的說就是AQS同步器裡面的State相當於一個計數器,如果某一個線程獲取鎖了以後再再次去獲取鎖,這個計算器State就會+1.後面的代碼中會詳細的描述到。
還有一個重要的點 就是lock和Condition的聯合使用,ReentrantLock可以創建一個Condition,這個我在條件隊列的文章中詳細描述過。

Synchronized對比

ReentrantLock是一個排他鎖,也就是同一個時刻只會有一個線程能獲取到鎖,這個主要利用的就是AQS的獨占模式實現的。ReentrantLock能保證在多線程的情況下的線程執行安全,那就會想到Synchronized的關鍵字。Synchronized是JVM實現的,ReentrantLock是由JDK實現的,ReentrantLock相對於比較靈活,可以設置時間等待,線程中斷,鎖投票等,但是一定用完記得要在finally手動釋放,Synchronized是JVM做自動釋放鎖的操作。

用法

/**
 * @ClassName ReentrantLockDemo
 * @Auther burgxun
 * @Description: 重入鎖的Demo
 * @Date 2020/4/5 14:21
 **/
public class ReentrantLockDemo {

    private static ReentrantLock reentrantLock = new ReentrantLock();

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            int finalI = i;
            new Thread(() -> {
                reentrantLock.lock();
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("獲取鎖的線程是:" + finalI);
                reentrantLock.unlock();
            }).start();
        }
    }
}

執行結果是:

org.example.ReentrantLockDemo
獲取鎖的線程是:1-開始執行
獲取鎖的線程是:1-執行結束
獲取鎖的線程是:2-開始執行
獲取鎖的線程是:2-執行結束
獲取鎖的線程是:4-開始執行
獲取鎖的線程是:4-執行結束
獲取鎖的線程是:3-開始執行
獲取鎖的線程是:3-執行結束
獲取鎖的線程是:5-開始執行
獲取鎖的線程是:5-執行結束

Process finished with exit code 0

從執行結果上 我們可以看到 一個線程執行完成釋放鎖後才能執行另外一個線程

源碼分析

看完了上面的簡介和用法,我們進入源碼去分析看下 是怎麼實現的

代碼結構

在這裡插入圖片描述

方法分析

從UML類圖上面我們可以看到 ReentrantLock有3個內部類,一個是抽象的靜態類Sync還有2個實現了Sync的類一個是非公平鎖的實現NonfairSync,還有一個是公平鎖的實現FairSync

Sync

首先我們看下Sync這個抽象類

abstract static class Sync extends AbstractQueuedSynchronizer {
        abstract void lock();

        /**
         * 非公平鎖鎖的tryAcquire的實現 AQS中tryAcquire方法的需要子類重寫
         */
        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();//獲取當前線程
            int c = getState();//獲取CAS的State值
            if (c == 0) {//如果等於0 說明可以獲取鎖
                if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 狀態
                    setExclusiveOwnerThread(current);//CAS修改成功後 設置當前線程為鎖的持有者
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {//判斷當前線程 是否是鎖的持有者線程
                int nextc = c + acquires;//新增重入次數
                if (nextc < 0) // overflow  //如果小於0 說明是出問題了 拋出異常
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

        /**
         * AQS 中方法的重寫 釋放資源
         */
        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;// 算下 當前同步器中state的差值
            //確保釋放和加鎖線程是同一個  上一篇中我也提到過
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;//是否完全了釋放資源
            if (c == 0) {//如果C的值是0 說明沒有線程擁有鎖了
                free = true;
                /*
                 * 設置擁有鎖的線程為null 因為現在鎖是沒線程暫用的  如果不修改 下次別的線程去獲取鎖的會有這個判斷
                 */
                setExclusiveOwnerThread(null);
            }
            setState(c);//修改 同步器的State 狀態
            return free;
        }

        /**
         * 判斷當前線程 是否是擁有鎖的線程一致  重寫了AQS的方法
         */
        protected final boolean isHeldExclusively() {
            // While we must in general read state before owner,
            // we don't need to do so to check if current thread is owner
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        final ConditionObject newCondition() {
            return new ConditionObject();
        }

        /**
         * 獲取當前重入鎖的擁有者
         */
        final Thread getOwner() {
            return getState() == 0 ? null : getExclusiveOwnerThread();
        }

        /**
         * 當前鎖占用的State 數量 也就是重入了幾次
         */
        final int getHoldCount() {
            return isHeldExclusively() ? getState() : 0;
        }

        /**
         * 重入鎖 是否可以被占用  State等於0 別的線程才能來搶占到鎖
         */
        final boolean isLocked() {
            return getState() != 0;
        }
    }

上面是我寫了註解的Sync的整個類,一會兒我們挨個分析下,從繼承關係 我們可以看到Sync是繼承於我們的AQS的,所以裡面很多底層的方法都是用的AQS裡面的實現,所以說呀 理解了AQS 那整個JUC下的鎖和各種線程安全的集合什麼的 看源碼都會輕鬆很多~

NonfairSync

//非公平鎖
    static final class NonfairSync extends Sync {

        @Override
        void lock() {
            if (compareAndSetState(0, 1))//如果CAS能設置成功 說明能獲取到鎖 就設置當前線程為鎖的owner  不公平的體現
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
        
        /**
         * 嘗試獲取資源,覆寫的AQS類裡面方法
         */
        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

FairSync

  //公平鎖
    static final class FairSync extends Sync {

        @Override
        void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                /*
                 * hasQueuedPredecessors 返回是否Sync中是否有在當前線程前面等待的線程 false沒有
                 * 如果false 那就CAS 修改State 成功後更新當前線程是鎖的擁有者線程
                 * */
                if (!hasQueuedPredecessors() &&
                        compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            } else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

非公平鎖VS公平鎖

什麼是公平非公平

看了上面的代碼 有人可能不明白什麼叫做TMD公平!!!哈哈
具體的代碼下文描述,我先舉個小例子,讓讀者帶入下:

話說大家在火車站排隊買票,小明和小強放寒假了,都去火車站提前買票準備回家,他們倆到了車站一看,售票視窗就開了一個,而且前面有3個人在排隊,那小明和小強沒辦法就只能排除在了隊伍裡面,等了5分鐘終於排到了小強,小強剛買完票,突然看到同個宿舍的小張也來買票了,立馬和小張說,來來來 這邊 我這邊可以買票,小張立馬插隊到了小強後面,強行去買了票,小明心裡嘀咕說居然插隊,素質真差,看你人高馬大的 就算了吧!哈哈~

其實上面的類子中 公平和非公平的體現就是 賣票的視窗只有一個,就像獲取獨占鎖,當有一個線程占有了鎖,那其餘的線程就必須在後面排隊等待,就像買票一樣,非公平的鎖的實現就相當於插隊,管你後面有沒有人 我都要去嘗試下買票

從上面的分析我們也能知道公平和非公平是指的是獲取資源時候的行為。

ReentrantLock

ReentrantLock的構造函數

/**
    * 預設實現非公平鎖
    */
   public ReentrantLock() {
       sync = new NonfairSync();
   }

   /**
    * 帶參數的構造函數
    */
   public ReentrantLock(boolean fair) {
       sync = fair ? new ReentrantLock.FairSync() : new ReentrantLock.NonfairSync();
   }

從上面可以看到ReentrantLock預設使用的是一個非公平鎖的實現

lock加鎖方法

    /**
     * 加鎖
     */
    @Override
    public void lock() {
        sync.lock();
    }

    /**
     * 加鎖 響應中斷
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override
    /**
     * 只做一次獲取鎖的嘗試  不會阻塞線程
     */
    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    @Override
    /**
     * 只做一次獲取鎖的嘗試  不會阻塞線程
     */
    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

非公平的加鎖

非公平的加鎖

從代碼中 我們看到lock方法調用的是Sync的lock方法,但是Sync中的lock方法是一個抽象方式是子類實現的
那我從NonfairSync類中找到了lock的具體實現,入下:

 void lock() {
         //如果CAS能設置成功 說明能獲取到鎖 就設置當前線程為鎖的owner  不公平的體現
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

首先方法一開始先執行一個CAS修改State的操作,如果能夠執行成功,說明當前的AQS同步器中的狀態值是0,那麼線程就可以占有鎖,然後設置當前線程為鎖的Owner線程.setExclusiveOwnerThread這個方法我在第一篇文章中也描述過,這個方法是在AQS的父類AbstractOwnableSynchronizer方法裡面的!
如果執行失敗,那麼方法就執行acquire方法:

 /**
     * 此方法位於AQS中
     * 獲取資源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    /**
     * 此方法位於NonfairSync中
     * 嘗試獲取資源,覆寫的AQS類裡面方法
     */
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
        
        
     /**
     * 此方法位於Sync中
     * 非公平鎖鎖的tryAcquire的實現 AQS中tryAcquire方法的需要子類重寫
     */
    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();//獲取當前線程
        int c = getState();//獲取CAS的State值
        if (c == 0) {//如果等於0 說明可以獲取鎖
            if (compareAndSetState(0, acquires)) {//CAS的方式 修改State 狀態
                setExclusiveOwnerThread(current);//CAS修改成功後 設置當前線程為鎖的持有者
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {//判斷當前線程 是否是鎖的持有者線程
            int nextc = c + acquires;//新增重入次數
            if (nextc < 0) // overflow  //如果小於0 說明是出問題了 拋出異常
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

acquire方法位於AQS中 此方法會首先調用tryAcquire方法去嘗試獲取下鎖,因為雖然lock方法一開始獲取鎖失敗了,可能這邊鎖又被別的線程釋放了,所以要再次嘗試獲取下鎖,具體tryAcquire怎麼做的 上面的代碼中我已經描述的很清楚了~

公平的加鎖

公平的加鎖

先看下代碼:

     /**
     * 公平鎖版本的加鎖
     */
    void lock() {
        acquire(1);
    }

    /**
     * 此方法位於AQS中
     * 獲取資源
     */
    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
        

    /**
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        if (c == 0) {
            /*
             * hasQueuedPredecessors 返回是否Sync中是否有在當前線程前面等待的線程 false沒有
             * 如果false 那就CAS 修改State 成功後更新當前線程是鎖的擁有者線程
             * */
            if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        } else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

首先這邊lock的時候,直接執行了acquire方法,而非和NonfairSync一樣,有個CAS的嘗試操作,這也是體現公平的一部分,和非公鎖的方法一樣的是這邊的acquire方法也是在AQS上中的,但是調用的tryAcquire方法 是自己fairSync類自己實現的,而非調用的Sync的預設實現,這邊唯一有區別的就是hasQueuedPredecessors 這個方法,hasQueuedPredecessors方法是獲取當前Sync隊列中是否還有別的等待線程,如果有 就算當前的狀態State是滿足條件的,也是要加入Sync等待隊列中的,這個是acquireQueued方法裡面做的事情,不清楚這個acquireQueued方法的,看看前面幾篇文章,我就不再贅述了~

unlock解鎖

@Override
    public void unlock() {
        sync.release(1);//調用的AQS裡面的方法
    }
    
     /**
     * 此方法在AQS中
     * 釋放當前資源
     * 喚醒等待線程去獲取資源
     */
    public final boolean release(int arg) {
        if (tryRelease(arg)) {//說明釋放資源成功
            Node h = head;//當前隊列裡面的head節點
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);//通知阻塞隊列裡面的head的next節點去獲取鎖
            return true;
        }
        return false;
    }
    
    
     /**
     * 此方法在Sync中
     * AQS 中方法的重寫 釋放資源  
     */
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;// 算下 當前同步器中state的差值
        //確保釋放和加鎖線程是同一個  上一篇中我也提到過
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;//是否完全了釋放資源
        if (c == 0) {//如果C的值是0 說明沒有線程擁有鎖了
            free = true;
            /*
             * 設置擁有鎖的線程為null 因為現在鎖是沒線程暫用的  如果不修改 下次別的線程去獲取鎖的會有這個判斷
             */
            setExclusiveOwnerThread(null);
        }
        setState(c);//修改 同步器的State 狀態
        return free;
    }
    

從源碼上看 解鎖的方法只有一個 因為公平鎖和非公共鎖 只是描述的是加鎖的行為,解鎖的行為其實都是一致的,都是釋放當前線程占用State值,然後喚醒SyncQueue的頭部Head節點的下一個節點去嘗試獲取鎖!

有些沒帖子上面的方法描述 請在前面AQS中的幾篇文章中看下 都詳細描述過~

總結

公平鎖 VS 非公平鎖

首先2者並沒有好壞之分,是要根據對應的場景選擇對應的鎖技術

公平鎖 則重的是公平性

非公平鎖 則重的是併發性

非公平鎖 是搶占式的,忽略了SyncQueue重其他的等待線程,線程在進入等待隊列之前會進行2次嘗試獲取鎖,這大大增加了獲取鎖的機會,這種好處體現在2個方面:

  • 線程不必加入等待隊列就可以獲取鎖,不僅免去了構造節點並加入隊列的繁瑣操作,同時也節省了線程阻塞的喚醒開銷,線程阻塞和喚醒涉及到線程上下文的切換和操作系統的系統調用,是非常耗時的。在高併發的情況下,如果線程持有鎖的時間非常短,短到線程入隊阻塞的過程超過了線程持有並釋放鎖的時間的開銷,那麼這種搶占式的特性對併發的性能的提高會很明顯
  • 減少CAS競爭,如果線程必須要加入阻塞隊列才能去獲取鎖,那麼入隊時的CAS競爭將變得異常的激烈,CAS操作雖然不會導致線程失敗而掛起,但不斷的失敗重試導致對CPU的浪費是不能忽略的

Synchronized VS ReentrantLock

從整個文章的分析來看,ReentrantLock是比Synchronized更加的靈活的,

  • ReentrantLock提供了更多 更全面的API 可以設置等待時間,可以中斷方法等,還提供了Trylock等非阻塞的方法
  • ReentrantLock還可以配和Condition一起使用,使得線程等待的時候更加靈活,可以設置不同的條件等待 等等

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

-Advertisement-
Play Games
更多相關文章
  • 最近c艹在學類與對象 擅長划水的我並沒有聽課,害,所以東西有點雲里霧裡, 所以寫下這一篇博客來理解一下。 類的使用就像是封裝一類的東西,定義為一個類 定義的地方和結構體其實還是有點相似的。 這東西在演算法裡面用的感覺不多, 以前也沒接觸過, 現在發現實際運用還是很有意思的。 首先是定義 class C ...
  • Set集合的功能和Collection是一致的。 HashSet:底層數據結構是哈希表,線程非同步。 HashSet保證元素唯一性:hashCode()和equals() 如果元素的hashCode值相同,才會判斷equals是否為true。 如果元素的hashCode值不同,不會調用equals。 ...
  • from wordcloud import WordCloud import matplotlib.pyplot as plt import jieba # 生成詞雲 def create_word_cloud(filename): with open('hongloumong.txt',encod ...
  • 為什麼要寫STL淺談這個系列,因為最近我在準備藍橋杯,刷題的時候經常要用到STL,準備補一補,但一直沒有找到一個好的視頻和資料,最開始準備跟著c語言中文網學,但覺得太繁雜了,最後在b站(b站上電腦類的教學視頻挺多的)上找了一個視頻學的。這個系列相當於我的一個整理。 這個系列只是淺談,但刷題應該夠了 ...
  • 這是我的第一篇隨筆,當然是發我寫的第一個游戲啦! 大一(本人現在大二)寒假過完年,在家待著想起放假前計劃寫一個游戲的,因為本人立志走游戲開發這條路,加上大一上冊學了C語言,就想寫個游戲練練手。想了很久,最後決定寫一個俄羅斯方塊。 萬事開頭難,一開始真的不知道從何下手,所以百度查了一些資料(程式猿必須 ...
  • 1. web.xml中url pattern配置 url pattern為/ 使用SpringMVC的框架時,需要在 配置前端控制器 ,配置為: url pattern為/ 我們需要使用過濾器時,需要在 中進行註冊。以處理中文亂碼的過濾器 為例,配置為: url pattern為 .do 使用Str ...
  • [TOC] pandas可以進行數據輸入和輸出,有以下幾種類型:讀取文本文件及硬碟上其他更高效的格式文件,從資料庫中載入數據,於網路資源進行交互(比如Web API)。 下麵進行不同文本文件的讀取和寫入操作講解,首先進行文本格式數據的讀寫講解。 一:文本格式數據的讀寫 將表格型數據讀取為DataFr ...
  • 原文: "Java Garbage Collection Algorithms [till Java 9]" 垃圾回收(Garbage collection,GC)一直是 Java 流行背後的重要特性之一。垃圾回收是 Java 中用於釋放未使用記憶體的機制。本質上,它跟蹤所有仍在使用的對象,並將其餘的 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...