深入淺出ReentrantReadWriteLock源碼解析

来源:https://www.cnblogs.com/pinxiong/archive/2020/07/17/13307774.html
-Advertisement-
Play Games

讀寫鎖實現邏輯相對比較複雜,但是卻是一個經常使用到的功能,希望將我對ReentrantReadWriteLock的源碼的理解記錄下來,可以對大家有幫助 前提條件 在理解ReentrantReadWriteLock時需要具備一些基本的知識 理解AQS的實現原理 之前有寫過一篇《深入淺出AQS源碼解析》 ...


讀寫鎖實現邏輯相對比較複雜,但是卻是一個經常使用到的功能,希望將我對ReentrantReadWriteLock的源碼的理解記錄下來,可以對大家有幫助

前提條件

在理解ReentrantReadWriteLock時需要具備一些基本的知識

理解AQS的實現原理

之前有寫過一篇《深入淺出AQS源碼解析》關於AQS的文章,對AQS原理不瞭解的同學可以先看一下

理解ReentrantLock的實現原理

ReentrantLock的實現原理可以參考《深入淺出ReentrantLock源碼解析》

什麼是讀鎖和寫鎖

對於資源的訪問就兩種形式:要麼是讀操作,要麼是寫操作。讀寫鎖是將被鎖保護的臨界資源的讀操作和寫操作分開,允許同時有多個線程同時對臨界資源進行讀操作,任意時刻只允許一個線程對資源進行寫操作。簡單的說,對與讀操作採用的是共用鎖,對於寫操作採用的是排他鎖

讀寫狀態的設計

ReentrantReadWriteLock是用state欄位來表示讀寫鎖重覆獲取資源的次數,高16位用來標記讀鎖的同步狀態,低16位用來標記寫鎖的同步狀態

// 劃分的邊界線,用16位來劃分
static final int SHARED_SHIFT   = 16;
// 讀鎖的基本單位,也就是讀鎖加1或者減1的基本單位(1左移16位後的值)
static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
// 讀寫鎖的最大值(在計算讀鎖的時候需要先右移16位)
static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
// 寫鎖的掩碼,state值與掩碼做與運算後得到寫鎖的真實值
static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;

// 獲取資源被讀鎖占用的次數
static int sharedCount(int c){
      return c >>> SHARED_SHIFT;
}

// 獲取資源被寫鎖占用的次數
static int exclusiveCount(int c){
      return c & EXCLUSIVE_MASK;
}

在統計讀鎖被每個線程持有的次數時,ReentrantReadWriteLock採用的是HoldCounter來實現的,具體如下:

// 持有讀鎖的線程重入的次數
static final class HoldCounter {
    // 重入的次數
    int count = 0;
    // 持有讀鎖線程的線程id
    final long tid = getThreadId(Thread.currentThread());
}

/**
 * 採用ThreadLocal機制,做到線程之間的隔離
 */
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}

/**
 * 線程持有可重入讀鎖的次數
 */
private transient ThreadLocalHoldCounter readHolds;

/**
 * 緩存最後一個成功獲取讀鎖的線程的重入次數,有兩方面的好處:
 * 1、避免了通過訪問ThreadLocal來獲取讀鎖的信息,這個優化的前提是
 *    假設多數情況下,一個獲取讀鎖的線程,使用完以後就會釋放讀鎖,
 *    也就是說最後獲取讀鎖的線程和最先釋放讀鎖的線程大多數情況下是同一個線程
 * 2、因為ThreadLocal中的key是一個弱引用類型,當有一個變數持有HoldCounter對象時,
 *    ThreadLocalHolderCounter中最後一個獲取鎖的線程信息不會被GC回收掉
 */
private transient HoldCounter cachedHoldCounter;

/**
 * 第一個獲取讀鎖的線程,有兩方面的考慮:
 * 1、記錄將共用數量從0變成1的線程
 * 2、對於無競爭的讀鎖來說進行線程重入次數數據的追蹤的成本是比較低的
 */
private transient Thread firstReader = null;

/**
 * 第一個獲取讀鎖線程的重入次數,可以參考firstReader的解析
 */
private transient int firstReaderHoldCount;

ReentrantReadWriteLock 源碼解析

ReentrantReadWriteLock 一共有5個內部類,具體如下:

  • Sync:公平鎖和非公平鎖的抽象類
  • NonfairSync:非公平鎖的具體實現
  • FairSync:公平鎖的具體實現
  • ReadLock:讀鎖的具體實現
  • WriteLock:寫鎖的具體實現

我們從讀鎖ReadLock和寫鎖WriteLock的源碼開始分析,然後順著這個思路將整個ReentrantReadWriteLock中所有的核心源碼(所有的包括內部類)進行分析。

ReadLock類源碼解析

public static class ReadLock implements Lock, java.io.Serializable {
    
    private final Sync sync;

    /**
     * 通過ReentrantReadWriteLock中的公平鎖或非公平鎖來初始化sync變數
     */
    protected ReadLock(ReentrantReadWriteLock lock) {
          sync = lock.sync;
    }

    /**
     * 阻塞的方式獲取鎖,因為讀鎖是共用鎖,所以調用acquireShared方法
     */
    public void lock() {
          sync.acquireShared(1);
    }

    /**
     * 可中斷且阻塞的方式獲取鎖
     */
    public void lockInterruptibly() throws InterruptedException {
          sync.acquireSharedInterruptibly(1);
    }

    /**
     * 超時嘗試獲取鎖,非阻塞的方式
     */
    public boolean tryLock(long timeout, TimeUnit unit)
          throws InterruptedException {
          return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
    }
    /**
     * 嘗試獲取寫鎖,非阻塞的方式
     */
    public boolean tryLock() {
          return sync.tryReadLock();
    }

    /**
     * 釋放鎖
     */
    public void unlock() {
          sync.releaseShared(1);
    }
}

接下來,我們重點看一下在公平鎖和非公平鎖下Sync.acquireSharedSync.releaseSharedSync.tryLock這3個方法的實現(acquireSharedInterruptiblytryAcquireSharedNanos是AQS中的方法,這裡就不在討論了,具體可以參考《深入淺出AQS源碼解析》),其中Sync.acquireShared中核心調用的方法是Sync.tryAcquireSharedSync. releaseShared中核心調用的方法是Sync.tryReleaseSharedSync.tryLock中核心調用的方法是Sync.tryReadLock,所以我們重點分析Sync.tryAcquireShared方法、Sync.tryReleaseShared方法和sync.tryReadLock方法

Sync.tryAcquireShared方法

protected final int tryAcquireShared(int unused) {
    /**
     * 以共用鎖的方式嘗試獲取讀鎖,步驟如下:
     * 1、如果資源已經被寫鎖獲取了,直接返回失敗
     * 2、如果讀鎖不需要等待(公平鎖和非公平鎖的具體實現有區別)、
     *    並且讀鎖未超過上限、同時設置讀鎖的state值成功,則返回成功
     * 3、如果步驟2失敗了,需要進入fullTryAcquireShared函數再次嘗試獲取讀鎖
     */
    Thread current = Thread.currentThread();
    int c = getState();
    /**
     * 資源已經被寫鎖獨占,直接返回false
     */
    if (exclusiveCount(c) != 0 &&
          getExclusiveOwnerThread() != current)
          return -1;
    int r = sharedCount(c);
    /**
     * 1、讀鎖不需要等待
     * 2、讀鎖未超過上限
     * 3、設置讀鎖的state值成功
     * 則返回成功
     */
    if (!readerShouldBlock() &&
          r < MAX_COUNT &&
          compareAndSetState(c, c + SHARED_UNIT)) {
          if (r == 0) {
            // 記錄第一個獲取讀鎖的線程信息
            firstReader = current;
            firstReaderHoldCount = 1;
          } else if (firstReader == current) {
            // 第一個獲取讀鎖的線程再次獲取鎖(重入)
            firstReaderHoldCount++;
          } else {
            // 修改獲取鎖的線程的重入的次數
            HoldCounter rh = cachedHoldCounter;
            if (rh == null ||
                rh.tid != LockSupport.getThreadId(current))
                cachedHoldCounter = rh = readHolds.get();
            else if (rh.count == 0)
                readHolds.set(rh);
            rh.count++;
          }
          return 1;
    }
    /**
     * 如果CAS失敗再次獲取讀鎖
     */
    return fullTryAcquireShared(current);
}

接下來看一下fullTryAcquireShared方法:

final int fullTryAcquireShared(Thread current) {
    /**
     * 調用該方法的線程都是希望獲取讀鎖的線程,有3種情況:
     * 1、在嘗試通過CAS操作修改state時由於有多個競爭讀鎖的線程導致CAS操作失敗
     * 2、需要排隊等待獲取讀鎖的線程(公平鎖)
     * 3、超過讀鎖限制的最大申請次數的線程
     */
    HoldCounter rh = null;
    for (;;) { // 無限迴圈獲取鎖
        int c = getState();
        // 已經被寫線程獲取鎖了,直接返回
        if (exclusiveCount(c) != 0) {
            if (getExclusiveOwnerThread() != current)
                return -1;
        // 需要被block的讀線程(公平鎖)
        } else if (readerShouldBlock()) {
            // 如果時當前線程
            if (firstReader == current) {
                // assert firstReaderHoldCount > 0;
            } else {
                // 清理當前線程中重入次數為0的數據
                if (rh == null) {
                    rh = cachedHoldCounter;
                    if (rh == null || rh.tid != getThreadId(current)) {
                        rh = readHolds.get();
                        if (rh.count == 0)
                            readHolds.remove();
                    }
                }
                // 當前線程獲取鎖失敗
                if (rh.count == 0)
                    return -1;
            }
        }
        // 判斷是否超過讀鎖的最大值
        if (sharedCount(c) == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 修改讀鎖的state值
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            // 最新獲取到讀鎖的線程設置相關的信息
            if (sharedCount(c) == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++; // 當前線程重覆獲取鎖(重入)
            } else {
                // 在readHolds中記錄獲取鎖的線程的信息
                if (rh == null)
                    rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
                cachedHoldCounter = rh; // cache for release
            }
            return 1;
        }
    }
}

Sync.tryReleaseShared方法

tryReleaseShared方法的實現邏輯比較簡單,我們直接看代碼中的註釋

protected final boolean tryReleaseShared(int unused) {
    Thread current = Thread.currentThread();
    /**
     * 如果當前線程是第一個獲取讀鎖的線程,有兩種情況:
     * 1、如果持有鎖的次數為1,直接釋放成功
     * 2、如果持有鎖的次數大於1,說明有重入的情況,需要次數減1
     */
    if (firstReader == current) {
        // assert firstReaderHoldCount > 0;
        if (firstReaderHoldCount == 1)
            firstReader = null;
        else
            firstReaderHoldCount--;
    } else {
      /**
       * 如果當前線程不是第一個獲取讀鎖的線程
       * 需要更新線程持有鎖的重入次數
       * 如果次數小於等於0說明有異常,因為只有當前線程才會出現持有鎖的重入次數等於0或者1
       */
        HoldCounter rh = cachedHoldCounter;
        if (rh == null || rh.tid != getThreadId(current))
            rh = readHolds.get();
        int count = rh.count;
        if (count <= 1) {
            readHolds.remove();
            if (count <= 0)
                throw unmatchedUnlockException();
        }
        --rh.count;
    }
    // 修改state的值
    for (;;) {
        int c = getState();
        int nextc = c - SHARED_UNIT;
        if (compareAndSetState(c, nextc))
            // 如果是最後一個釋放讀鎖的線程nextc為0,否則不是
            return nextc == 0;
    }
}

sync.tryReadLock方法

tryReadLock的代碼比較簡單,就直接在將解析過程在註釋中描述

final boolean tryReadLock() {
    Thread current = Thread.currentThread();
    for (;;) { // 無限迴圈獲取讀鎖
        int c = getState();
        // 當前線程不是讀線程,直接返回失敗
        if (exclusiveCount(c) != 0 &&
            getExclusiveOwnerThread() != current)
            return false;
        int r = sharedCount(c);
        // 讀鎖的總重入次數是否超過最大次數限制
        if (r == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        /**
         * 通過CAS操作設置state的值,如果成功表示嘗試獲取讀鎖成功,需要做以下幾件事情:
         * 1、如果是第一獲取讀鎖要記錄第一個獲取讀鎖的線程信息
         * 2、如果是當前獲取鎖的線程和第一次獲取鎖的線程相同,需要更新第一獲取線程的重入次數
         * 3、更新獲取讀鎖線程相關的信息
         */
        if (compareAndSetState(c, c + SHARED_UNIT)) {
            if (r == 0) {
                firstReader = current;
                firstReaderHoldCount = 1;
            } else if (firstReader == current) {
                firstReaderHoldCount++;
            } else {
                HoldCounter rh = cachedHoldCounter;
                if (rh == null || rh.tid != getThreadId(current))
                    cachedHoldCounter = rh = readHolds.get();
                else if (rh.count == 0)
                    readHolds.set(rh);
                rh.count++;
            }
            return true;
        }
    }
}

WriteLock類源碼解析

public static class WriteLock implements Lock, java.io.Serializable {
    private final Sync sync;

    /**
     * 通過ReentrantReadWriteLock中的公平鎖或非公平鎖來初始化sync變數
     */
    protected WriteLock(ReentrantReadWriteLock lock) {
        sync = lock.sync;
    }

    /**
     * 阻塞的方式獲取寫鎖
     */
    public void lock() {
        sync.acquire(1);
    }

    /**
     * 中斷的方式獲取寫鎖
     */
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    /**
     * 嘗試獲取寫鎖
     */
    public boolean tryLock( ) {
        return sync.tryWriteLock();
    }

    /**
     * 超時嘗試獲取寫鎖
     */
    public boolean tryLock(long timeout, TimeUnit unit)
            throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }

    /**
     * 釋放寫鎖
     */
    public void unlock() {
        sync.release(1);
    }
}

接下來,我們重點看一下在公平鎖和非公平鎖下Sync.tryAcquireSync.tryReleaseSync.tryWriteLock這幾個核心方法是如何實現寫鎖的功能

Sync.tryAcquire方法

Sync.tryAcquire方法的邏輯比較簡單,就直接在代碼中註釋,具體如下:

protected final boolean tryAcquire(int acquires) {
    Thread current = Thread.currentThread();
    // 讀寫鎖的次數
    int c = getState();
    // 寫鎖的次數
    int w = exclusiveCount(c);
    /*
     * 如果讀寫鎖的次數不為0,說明鎖可能有以下3中情況:
     * 1、全部是讀線程占用資源
     * 2. 全部是寫線程占用資源
     * 3. 讀寫線程都占用了資源(鎖降級:持有寫鎖的線程可以去持有讀鎖),但是讀寫線程都是同一個線程
     */
    if (c != 0) {
        // 寫線程不占用資源,第一個獲取鎖的線程也不是當前線程,直接獲取失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 檢查獲取寫鎖的線程是否超過了最大的重入次數
        if (w + exclusiveCount(acquires) > MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
        // 修改state的狀態,之所以沒有用CAS操作來修改,是因為寫線程只有一個,是獨占的
        setState(c + acquires);
        return true;
    }
    /*
     * 寫線程是第一個競爭鎖資源的線程
     * 如果寫線程需要等待(公平鎖的情況),或者
     * 寫線程的state設置失敗,直接返回false
     */
    if (writerShouldBlock() ||
        !compareAndSetState(c, c + acquires))
        return false;
    // 設置當前線程為owner
    setExclusiveOwnerThread(current);
    return true;
}

Sync.tryRelease方法

Sync.tryRelease方便的代碼很簡單,直接看代碼中的註釋

protected final boolean tryRelease(int releases) {
    // 如果釋放鎖的線程不持有鎖,返回失敗
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    // 獲取寫鎖的重入的次數
    int nextc = getState() - releases;
    // 如果次數為0,需要釋放鎖的owner
    boolean free = exclusiveCount(nextc) == 0;
    if (free)
        setExclusiveOwnerThread(null); // 釋放鎖的owner
    setState(nextc);
    return free;
}

Sync.tryWriteLock方法

Sync.tryWriteLock這個方法也比較簡單,就直接上代碼了

final boolean tryWriteLock() {
    Thread current = Thread.currentThread();
    int c = getState();
    // 讀鎖或者寫鎖已經被線程持有
    if (c != 0) {
        int w = exclusiveCount(c);
        // 寫鎖第一次獲取鎖或者當前線程不是第一次獲取寫鎖的線程(也就是不是owner),直接失敗
        if (w == 0 || current != getExclusiveOwnerThread())
            return false;
        // 超出寫鎖的最大次數,直接失敗
        if (w == MAX_COUNT)
            throw new Error("Maximum lock count exceeded");
    }
    // 競爭寫鎖的線程修改state,
    // 如果成功將自己設置成鎖的owner,
    // 如果失敗直接返回
    if (!compareAndSetState(c, c + 1))
        return false;
    setExclusiveOwnerThread(current); // 設置當前線程持有鎖
    return true;
}

總結

  • 讀鎖和寫鎖的占用(重入)次數都是共用state欄位,高位記錄讀鎖,地位記錄寫鎖,所以讀鎖和寫鎖的最大占用次數為2^16
  • 讀鎖和寫鎖都是可重入的
  • 讀鎖是共用鎖,允許多個線程獲取
  • 寫鎖是排他鎖,只允許一個線程獲取
  • 一個線程獲取了讀鎖,在非公平鎖的情況下,其他等待獲取讀鎖的線程都可以嘗試獲取讀鎖,在公平鎖的情況下,按照AQS同步隊列的順利來獲取,如果隊列前面有一個等待寫鎖的線程在排隊,則後面所有等待獲取讀鎖的線程都將無法獲取讀鎖
  • 獲取讀鎖的線程,不能再去申請獲取寫鎖
  • 一個獲取了寫鎖的線程,在持有鎖的時候可以去申請獲取讀鎖,在釋放寫鎖以後,還會繼續持有讀鎖,這就是所謂的鎖降級
  • 讀鎖無法升級為寫鎖,原因是獲取讀鎖的線程可能是多個,而寫鎖是獨占的,不能多個線程持有,也就是說不支持鎖升級

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

-Advertisement-
Play Games
更多相關文章
  • 1.同步塊 同步塊(synchronized(obj)){ },obj稱之為同步監視器 1.obj可以是任何對象,但是推薦使用共用資源作為同步監視器 2.同步方法中無需指定同步監視器,因為同步方法的同步監視器時this即該對象本身,或class即類的模子 同步監視器執行過程 1.第一個線程訪問,鎖定 ...
  • 前言 伺服器好端端的竟然中了挖礦病毒!!! 可憐我那 1 核 2 G 的伺服器,又弱又小,卻還免除不了被拉去當礦工的命運,實在是慘啊慘。 事情原來是這樣的。。。 就在今天下午,我準備登陸自己的遠程伺服器搞點東西的時候,突然發現 ssh 登陸不上了。 如上,提示被拒絕。這個問題很明顯就是伺服器沒有我的 ...
  • 前言 每一個孩子都像星空中的一顆星星,散髮著自己所特有的光芒照亮著整個夜空。今天就帶大家用27行Python代碼繪製一幅滿天星吧。 全局設置 在繪製滿天星的過程中要運用到turtle工具,它是Python的標準庫,也可以形象的稱它為海龜庫,它可以描繪繪圖的軌跡,操作簡單、快捷。首先,我們要做一些有關 ...
  • 話說Laravel7便捷的字元串操作 用過Laravel的朋友都知道,Laravel內置的字元串處理函數有瞭解,Illuminate\Support\Str類。 Laravel 7 現在基於這些函數提供了一個更加面向對象的、更加流暢的字元串操作庫。你可以使用 String::of 創建一個 Illu ...
  • 一、新建項目示例使用IDEA快速創建基於SpringBoot的工程。springboot 2.3.1java 8WebFlux 必須選用Reactive的庫POM 依賴 org.springframework.boot spring-boot-starter-webflux 二、Controller... ...
  • 前言 Dubbo是一個分散式服務框架,致力於提供高性能和透明化的RPC遠程服務調用方案,以及SOA服務治理方案。簡單的說,dubbo就是個服務框架,如果沒有分散式的需求,其實是不需要用的,只有在分散式的時候,才有dubbo這樣的分散式服務框架的需求,並且本質上是個服務調用的東東,說白了就是個遠程服務 ...
  • 條件語句 Go的條件語句和其它語言類似,主要是不支持三目運算符所以?:這種條件判斷是不支持的。Go提供的條件判斷語句主要有 if 還有 switch這兩種形式下麵是 if條件語句 if的幾種寫法,基本上和其它語言是一致的 if 條件 { } else { } if 條件 { } else if 條件 ...
  • 上次我們說到 微服務架構的前世今生(七):微服務架構生態體系 ,這次我們在說說微服務架構的技術支持。作者 哈嘍沃德先生,歡迎關註。 一、Spring Cloud Spring Cloud Netflix Eureka:服務註冊中心。 Spring Cloud Zookeeper:服務註冊中心。 Sp ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...