主頁 > 移動端開發 > 我告訴你一個 AtomicInteger 的驚天大秘密

我告訴你一個 AtomicInteger 的驚天大秘密

2020-09-23 08:13:31 移動端開發

i++ 不是執行緒安全的操作,因為它不是一個原子性操作,

那么,如果我想要達到類似 i++ 的這種效果,我應該使用哪些集合或者說工具類呢?

在 JDK1.5 之前,為了確保在多執行緒下對某基本資料型別或者參考資料型別運算的原子性,必須依賴于外部關鍵字 synchronized,但是這種情況在 JDK1.5 之后發生了改觀,當然你依然可以使用 synchronized 來保證原子性,我們這里所說的一種執行緒安全的方式是原子性的工具類,比如 AtomicInteger、AtomicBoolean 等,這些原子類都是執行緒安全的工具類,他們同時也是 Lock-Free 的,下面我們就來一起認識一下這些工具類以及 Lock - Free 是個什么概念,

了解 AtomicInteger

AtomicInteger 是 JDK 1.5 新添加的工具類,我們首先來看一下它的繼承關系

AtomicInteger01

與 int 的包裝類 Integer 一樣,都是繼承于 Number 類的,

AtomicInteger02

這個 Number 類是基本資料型別的包裝類,一般和資料型別有關的物件都會繼承于 Number 類,

它的繼承體系很簡單,下面我們來看一下它的基本屬性和方法

AtomicInteger 的基本屬性

AtomicInteger 的基本屬性有三個

wTiyJH.png

Unsafesun.misc 包下面的類,AtomicInteger 主要是依賴于 sun.misc.Unsafe 提供的一些 native 方法保證操作的原子性,

Unsafe 的 objectFieldOffset 方法可以獲取成員屬性在記憶體中的地址相對于物件記憶體地址的偏移量,說得簡單點就是找到這個變數在記憶體中的地址,便于后續通過記憶體地址直接進行操作,這個值就是 value

這個我們后面會再細說

value 就是 AtomicIneger 的值,

AtomicInteger 的構造方法

繼續往下看,AtomicInteger 的構造方法只有兩個,一個是無引數的構造方法,無引數的構造方法默認的 value 初始值是 0 ,帶引數的構造方法可以指定初始值,

AtomicInteger04

AtomicInteger 中的方法

下面我們就來聊一下 AtomicInteger 中的方法,

Get 和 Set

我們首先來看一下最簡單的 get 、set 方法:

get() : 獲取當前 AtomicInteger 的值

set() : 設定當前 AtomicInteger 的值

get() 可以原子性的讀取 AtomicInteger 中的資料,set() 可以原子性的設定當前的值,因為 get() 和 set() 最終都是作用于 value 變數,而 value 是由 volatile 修飾的,所以 get 、set 相當于都是對記憶體進行讀取和設定,如下圖所示

AtomicInteger05

我們上面提到了 i++ 和 i++ 的非原子性操作,我們說可以使用 AtomicInteger 中的方法進行替換,

Incremental 操作

AtomicInteger 中的 Incremental 相關方法可以滿足我們的需求

  • getAndIncrement() : 原子性的增加當前的值,并把結果回傳,相當于 i++ 的操作,
image-20200911085857825

為了驗證是不是執行緒安全的,我們用下面的例子進行測驗

public class TAtomicTest implements Runnable{

    AtomicInteger atomicInteger = new AtomicInteger();

    @Override
    public void run() {
        for(int i = 0;i < 10000;i++){
            System.out.println(atomicInteger.getAndIncrement());
        }
    }
    public static void main(String[] args) {

        TAtomicTest tAtomicTest = new TAtomicTest();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();
    }

}

通過輸出結果你會發現它是一個執行緒安全的操作,你可以修改 i 的值,但是最后的結果仍然是 i - 1,因為先取值,然后再 + 1,它的示意圖如下,

AtomicInteger06
  • incrementAndGet 與此相反,首先執行 + 1 操作,然后回傳自增后的結果,該操作方法能夠確保對 value 的原子性操作,如下圖所示
AtomicInteger07

Decremental 操作

與此相對,x-- 或者 x = x - 1 這樣的自減操作也是原子性的,我們仍然可以使用 AtomicInteger 中的方法來替換

  • getAndDecrement : 回傳當前型別的 int 值,然后對 value 的值進行自減運算,下面是測驗代碼
class TAtomicTestDecrement implements Runnable{

    AtomicInteger atomicInteger = new AtomicInteger(20000);

    @Override
    public void run() {
        for(int i = 0;i < 10000 ;i++){
            System.out.println(atomicInteger.getAndDecrement());
        }
    }

    public static void main(String[] args) {

        TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement();

        Thread t1 = new Thread(tAtomicTest);
        Thread t2 = new Thread(tAtomicTest);
        t1.start();
        t2.start();

    }

}

下面是 getAndDecrement 的示意圖

AtomicInteger08
  • decrementAndGet:同樣的,decrementAndGet 方法就是先執行遞減操作,然后再獲取 value 的值,示意圖如下
AtomicInteger09

LazySet 方法

volatile 有記憶體屏障你知道嗎?

記憶體屏障是啥啊?

記憶體屏障,也稱記憶體柵欄,記憶體柵障,屏障指令等, 是一類同步屏障指令,是 CPU 或編譯器在對記憶體隨機訪問的操作中的一個同步點,使得此點之前的所有讀寫操作都執行后才可以開始執行此點之后的操作,也是一個讓CPU 處理單元中的記憶體狀態對其它處理單元可見的一項技術,

CPU 使用了很多優化,使用快取、指令重排等,其最終的目的都是為了性能,也就是說,當一個程式執行時,只要最終的結果是一樣的,指令是否被重排并不重要,所以指令的執行時序并不是順序執行的,而是亂序執行的,這就會帶來很多問題,這也促使著記憶體屏障的出現,

語意上,記憶體屏障之前的所有寫操作都要寫入記憶體;記憶體屏障之后的讀操作都可以獲得同步屏障之前的寫操作的結果,因此,對于敏感的程式塊,寫操作之后、讀操作之前可以插入記憶體屏障,

記憶體屏障的開銷非常輕量級,但是再小也是有開銷的,LazySet 的作用正是如此,它會以普通變數的形式來讀寫變數,

也可以說是:懶得設定屏障了

AtomicInteger10

GetAndSet 方法

以原子方式設定為給定值并回傳舊值,

它的原始碼就是呼叫了一下 unsafe 中的 getAndSetInt 方法,如下所示

AtomicInteger11

就是先進行回圈,然后呼叫 getIntVolatile 方法,這個方法我在 cpp 中沒有找到,找到的小伙伴們記得及時告訴讓我學習一下,

回圈直到 compareAndSwapInt 回傳 false,這就說明使用 CAS 并沒有更新為新的值,所以 var5 回傳的就是最新的記憶體值,

CAS 方法

我們一直常說的 CAS 其實就是 CompareAndSet 方法,這個方法顧名思義,就是 比較并更新 的意思,當然這是字面理解,字面理解有點偏差,其實人家的意思是先比較,如果滿足那么再進行更新,

AtomicInteger12

上面給出了 CAS Java 層面的原始碼,JDK 官方給它的解釋就是 如果當前值等于 expect 的值,那么就以原子性的方式將當前值設定為 update 給定值,這個方法會回傳一個 boolean 型別,如果是 true 就表示比較并更新成功,否則表示失敗,

CAS 同時也是一種無鎖并發機制,也稱為 Lock Free,所以你覺得 Lock Free 很高大上嗎?并沒有,

下面我們構建一個加鎖解鎖的 CASLock

class CASLock {

    AtomicInteger atomicInteger = new AtomicInteger();
    Thread currentThread = null;

    public void tryLock() throws Exception{

        boolean isLock = atomicInteger.compareAndSet(0, 1);
        if(!isLock){
            throw new Exception("加鎖失敗");
        }

        currentThread = Thread.currentThread();
        System.out.println(currentThread + " tryLock");

    }

    public void unlock() {

        int lockValue = atomicInteger.get();
        if(lockValue == 0){
            return;
        }
        if(currentThread == Thread.currentThread()){
            atomicInteger.compareAndSet(1,0);
            System.out.println(currentThread + " unlock");
        }
    }

    public static void main(String[] args) {

        CASLock casLock = new CASLock();

        for(int i = 0;i < 5;i++){

            new Thread(() -> {
                try {
                    casLock.tryLock();
                    Thread.sleep(10000);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    casLock.unlock();
                }
            }).start();
        }

    }
}

在上面的代碼中,我們構建了一個 CASLock,在 tryLock 方法中,我們先使用 CAS 方法進行更新,如果更新不成功則拋出例外,并把當前執行緒設定為加鎖執行緒,在 unLock 方法中,我們先判斷當前值是否為 0 ,如果是 0 就是我們愿意看到的結果,直接回傳,否則是 1,則表示當前執行緒還在加鎖,我們再來判斷一下當前執行緒是否是加鎖執行緒,如果是則執行解鎖操作,

那么我們上面提到的 compareAndSet,它其實可以決議為如下操作

// 偽代碼

// 當前值
int v = 0;
int a = 0;
int b = 1;

if(compare(0,0) == true){
  set(0,1);
}
else{
  // 繼續向下執行
}

也可以拿生活場景中的買票舉例子,你去景區旅游肯定要持票才能進,如果你拿著是假票或者不符合景區的票肯定是能夠被識別出來的,如果你沒有拿票拿你也肯定進不去景區,

廢話少說,這就祭出來 compareAndSet 的示意圖

AtomicInteger14
  • weakCompareAndSet: 媽的非常認真看了好幾遍,發現 JDK1.8 的這個方法和 compareAndSet 方法完全一摸一樣啊,坑我,,,
AtomicInteger15

但是真的是這樣么?并不是,JDK 原始碼很博大精深,才不會設計一個重復的方法,你想想 JDK 團隊也不是會犯這種低級團隊,但是原因是什么呢?

《Java 高并發詳解》這本書給出了我們一個答案

AtomicInteger16

AddAndGet

AddAndGet 和 getAndIncrement、getAndAdd、incrementAndGet 等等方法都是使用了 do … while + CAS 操作,其實也就相當于是一個自旋鎖,如果 CAS 修改成功就會一直回圈,修改失敗才會回傳,示意圖如下

AtomicInteger17

深入 AtomicInteger

我們上面探討了 AtomicInteger 的具體使用,同時我們知道 AtomicInteger 是依靠 volatile 和 CAS 來保證原子性的,那么我們下面就來分析一下為什么 CAS 能夠保證原子性,它的底層是什么?AtomicInteger 與樂觀鎖又有什么關系呢?

AtomicInteger 的底層實作原理

我們再來瞧瞧這個可愛的 compareAndSetL(CAS) 方法,為什么就這兩行代碼就保證原子性了?

AtomicInteger18

我們可以看到,這個 CAS 方法相當于是呼叫了 unsafe 中的 compareAndSwapInt 方法,我們進到 unsafe 方能發中看一下具體實作,

AtomicInteger19

compareAndSwapInt 是 sun.misc 中的方法,這個方法是一個 native 方法,它的底層是 C/C++ 實作的,所以我們需要看 C/C++ 的原始碼,

知道 C/C++ 的牛逼之處了么,使用 Java 就是玩應用和架構的,C/C++ 是玩服務器、底層的,

compareAndSwapInt 的原始碼在 jdk8u-dev/hotspot/src/share/vm/prims/unsafe.app 路徑下,它的原始碼實作是

AtomicInteger20

也就是 Unsafe_CompareAndSwapInt 方法,我們找到這個方法

AtomicInteger21

C/C++ 原始碼我也看不懂,但是這不妨礙我們找到關鍵代碼 Atomic::cmpxchg ,cmpxchg 是 x86 CPU 架構的匯編指令,它的主要作用就是比較并交換運算元,我們繼續往下跟找一下這個指令的定義,

我們會發現對應不同的 os,其底層實作方式不一樣

AtomicInteger22

我們找到 Windows 的實作方式如下

AtomicInteger23

我們繼續向下找,它其實定義的是第 216 行的代碼,我們找進去

AtomicInteger24

此時就需要匯編指令和暫存器相關的知識了,

上面的 os::is-MP() 是多處理作業系統的介面,下面是 __asm ,它是 C/C++ 的關鍵字,用于呼叫行內匯編程式,

__asm 中的代碼是匯編程式,大致來說就是把 dest、exchange_value 、compare_value 的值都放在暫存器中,下面的 LOCK_IF_MP 中代碼的大致意思就是

AtomicInteger25

如果是多處理器的話就會執行 lock,然后進行比較操作,其中的 cmp 表示比較,mp 表示的就是 MultiProcessje 表示相等跳轉,L0 表示的是標識位,

我們回到上面的匯編指令,我們可以看到,CAS 的底層就是 cmpxchg 指令,

樂觀鎖

你有沒有這個疑問,為什么 AtomicInteger 可以獲取當前值,那為什么還會出現 expectValuevalue 不一致的情況呢?

因為 AtomicInteger 只是一個原子性的工具類,它不具有排他性,它不像是 synchronized 或者是 lock 一樣具有互斥和排他性,還記得 AtomicInteger 中有兩個方法 get 和 set 嗎?它們只是用 volatile 修飾了一下,而 volatile 不具有原子性,所以可能會存在 expectValue 和 value 的當前值不一致的情況,因此可能會出現重復修改,

針對上面這種情況的解決辦法有兩種,一種是使用 synchronizedlock 等類似的加鎖機制,這種鎖具有獨占性,也就是說同一時刻只能有一個執行緒來進行修改,這種方式能夠保證原子性,但是相對開銷比較大,這種鎖也叫做悲觀鎖,另外一種解決辦法是使用版本號或者是 CAS 方法

版本號

版本號機制是在資料表中加上一個 version 欄位來實作的,表示資料被修改的次數,當執行寫操作并且寫入成功后,version = version + 1,當執行緒 A 要更新資料時,在讀取資料的同時也會讀取 version 值,在提交更新時,若剛才讀取到的 version 值為當前資料庫中的 version 值相等時才更新,否則重試更新操作,直到更新成功,

CAS 方法

還有一種方式就是 CAS 了,我們上面用了大量的篇幅來介紹 CAS 方法,那么我們認為你現在已經對其運行機制有一定的了解了,我們就不再闡述它的運行機制了,

任何事情都是有利也有弊,軟體行業沒有完美的解決方案只有最優的解決方案,所以樂觀鎖也有它的弱點和缺陷,那就是 ABA 問題,

ABA 問題

ABA 問題說的是,如果一個變數第一次讀取的值是 A,準備好需要對 A 進行寫操作的時候,發現值還是 A,那么這種情況下,能認為 A 的值沒有被改變過嗎?可以是由 A -> B -> A 的這種情況,但是 AtomicInteger 卻不會這么認為,它只相信它看到的,它看到的是什么就是什么,舉個例子來說

假如現在有一個單鏈表,如下圖所示

AtomicInteger26

A.next = B ,B.next = null,此時有兩個執行緒 T1 和 T2 分別從單鏈表中取出 A ,由于一些特殊原因,T2 把 A 改為 B ,然后又改為 A ,此時 T1 執行 CAS 方法,發現單鏈表仍然是 A ,就會執行 CAS 方法,雖然結果沒錯,但是這種操作會造成一些潛在的問題,

AtomicInteger27

此時還是一個單鏈表,兩個執行緒 T1 和 T2 分別從單鏈表中取出 A ,然后 T1 把鏈表改為 ACD 如下圖所示

AtomicInteger28

此時 T2,發現記憶體值還是 A ,就會把 A 的值嘗試替換為 B ,因為 B 的參考是 null,此時就會造成 C、D 處于游離態

AtomicInteger29

JDK 1.5 以后的 AtomicStampedReference類就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當前值是否等于預期值,判斷的標準就是當前參考和郵戳分別和預期參考和郵戳相等,如果全部相等,則以原子方式設定為給定的更新值,

AtomicInteger30

好了,上面就是 Java 代碼流程了,看到 native 我們知道又要擼 cpp 了,開擼

AtomicInteger31

簡單解釋一下就是 UnsafeWrapper 就是包裝器,換個名字而已,然后經過一些 JNI 的處理,因為 compareAndSwapOject 比較的是參考,所以需要經過 C++ 面向物件的轉換,最主要的方法是 atomic_compare_exchange_oop

AtomicInteger32

可以看到,又出現了熟悉的詞匯 cmpxchg ,也就是說 compareAndSwapOject 使用的還是 cmpxchg 原子性指令,只是它經過了一系列轉換,

后記

拋出來一個問題,CAS 能保證變數之間的可見性么?為什么?

還有一個問題,getIntVolatile 方法的 cpp 原始碼在哪里?怎么找?

如果上面大佬們對這兩個問題有興趣,歡迎交流,

關注公眾號 程式員cxuan 回復 cxuan 領取優質資料,

我自己寫了六本 PDF ,非常硬核,鏈接如下

我自己寫了六本 PDF ,非常硬核,鏈接如下

我自己寫了六本 PDF ,非常硬核,鏈接如下

cxuan 嘔心瀝血肝了四本 PDF,

cxuan 又肝了兩本 PDF

轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/110414.html

標籤:其他

上一篇:[記錄三]Vue+node+koa2+mysql+nginx+redis,全堆疊開發小程式和管理員管理系統專案——token校驗登錄態

下一篇:一夜之間火爆GitHub的好文!!阿里資深架構師整理分享(內部)的SpringSecurity實戰技術檔案,看完我立馬跪了!!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【從零開始擼一個App】Dagger2

    Dagger2是一個IOC框架,一般用于Android平臺,第一次接觸的朋友,一定會被搞得暈頭轉向。它延續了Java平臺Spring框架代碼碎片化,注解滿天飛的傳統。嘗試將各處代碼片段串聯起來,理清思緒,真不是件容易的事。更不用說還有各版本細微的差別。 與Spring不同的是,Spring是通過反射 ......

    uj5u.com 2020-09-10 06:57:59 more
  • Flutter Weekly Issue 66

    新聞 Flutter 季度調研結果分享 教程 Flutter+FaaS一體化任務編排的思考與設計 詳解Dart中如何通過注解生成代碼 GitHub 用對了嗎?Flutter 團隊分享如何管理大型開源專案 插件 flutter-bubble-tab-indicator A Flutter librar ......

    uj5u.com 2020-09-10 06:58:52 more
  • Proguard 常用規則

    介紹 Proguard 入口,如何查看輸出,如何使用 keep 設定入口以及使用實體,如何配置壓縮,混淆,校驗等規則。

    ......

    uj5u.com 2020-09-10 06:59:00 more
  • Android 開發技術周報 Issue#292

    新聞 Android即將獲得類AirDrop功能:可向附近設備快速分享檔案 谷歌為安卓檔案管理應用引入可安全隱藏資料的Safe Folder功能 Android TV新主界面將顯示電影、電視節目和應用推薦內容 泄露的Android檔案暗示了傳說中的谷歌Pixel 5a與折疊屏新機 谷歌發布Andro ......

    uj5u.com 2020-09-10 07:00:37 more
  • AutoFitTextureView Error inflating class

    報錯: Binary XML file line #0: Binary XML file line #0: Error inflating class xxx.AutoFitTextureView 解決: <com.example.testy2.AutoFitTextureView android: ......

    uj5u.com 2020-09-10 07:00:41 more
  • 根據Uri,Cursor沒有獲取到對應的屬性

    Android: 背景:呼叫攝像頭,拍攝視頻,指定保存的地址,但是回傳的Cursor檔案,只有名稱和大小的屬性,沒有其他諸如時長,連ID屬性都沒有 使用 cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.Video.Media.DURATIO ......

    uj5u.com 2020-09-10 07:00:44 more
  • Android連載29-持久化技術

    一、持久化技術 我們平時所使用的APP產生的資料,在記憶體中都是瞬時的,會隨著斷電、關機等丟失資料,因此android系統采用了持久化技術,用于存盤這些“瞬時”資料 持久化技術包括:檔案存盤、SharedPreference存盤以及資料庫存盤,還有更復雜的SD卡記憶體儲。 二、檔案存盤 最基本存盤方式, ......

    uj5u.com 2020-09-10 07:00:47 more
  • Android Camera2Video整合到自己專案里

    背景: Android專案里呼叫攝像頭拍攝視頻,原本使用的 MediaStore.ACTION_VIDEO_CAPTURE, 后來因專案需要,改成了camera2 1.Camera2Video 官方demo有點問題,下載后,不能直接整合到專案 問題1.多次拍攝視頻崩潰 問題2.雙擊record按鈕, ......

    uj5u.com 2020-09-10 07:00:50 more
  • Android 開發技術周報 Issue#293

    新聞 谷歌為Android TV開發者提供多種新功能 Android 11將自動填表功能整合到鍵盤輸入建議中 谷歌宣布Android Auto即將支持更多的導航和數字停車應用 谷歌Pixel 5只有XL版本 搭載驍龍765G且將比Pixel 4更便宜 [圖]Wear OS將迎來重磅更新:應用啟動時間 ......

    uj5u.com 2020-09-10 07:01:38 more
  • 海豚星空掃碼投屏 Android 接收端 SDK 集成 六步驟

    掃碼投屏,開放網路,獨占設備,不需要額外下載軟體,微信掃碼,發現設備。支持標準DLNA協議,支持倍速播放。視頻,音頻,圖片投屏。好點意思。還支持自定義基于 DLNA 擴展的操作動作。好像要收費,沒體驗。 這里簡單記錄一下集成程序。 一 跟目錄的build.gradle添加私有mevan倉庫 mave ......

    uj5u.com 2020-09-10 07:01:43 more
最新发布
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:40:31 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:40:11 more
  • 歡迎頁輪播影片

    如圖,引導開始,球從上落下,同時淡入文字,然后文字開始輪播,最后一頁時停止,點擊進入首頁。 在來看看效果圖。 重力球先不講,主要歡迎輪播簡單實作 首先新建一個類 TextTranslationXGuideView,用于影片展示 文本是類似的,最后會有個圖片箭頭影片,布局很簡單,就是一個 TextVi ......

    uj5u.com 2023-04-20 08:39:36 more
  • 【FAQ】關于華為推送服務因營銷訊息頻次管控導致服務通訊類訊息

    一. 問題描述 使用華為推送服務下發IM訊息時,下發訊息請求成功且code碼為80000000,但是手機總是收不到訊息; 在華為推送自助分析(Beta)平臺查看發現,訊息發送觸發了頻控。 二. 問題原因及背景 2023年1月05日起,華為推送服務對咨詢營銷類訊息做了單個設備每日推送數量上限管理,具體 ......

    uj5u.com 2023-04-20 08:39:13 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:16:23 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:16:15 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:15:46 more
  • iOS從UI記憶體地址到讀取成員變數(oc/swift)

    開發除錯時,我們發現bug時常首先是從UI顯示發現例外,下一步才會去定位UI相關連的資料的。XCode有給我們提供一系列debug工具,但是很多人可能還沒有形成一套穩定的除錯流程,因此本文嘗試解決這個問題,順便提出一個暴論:UI顯示例外問題只需要兩個步驟就能完成定位作業的80%: 定位例外 UI 組 ......

    uj5u.com 2023-04-19 09:14:53 more
  • FIDE重磅更新!性能飛躍!體驗有禮!

    FIDE 開發者工具重構升級啦!實作500%性能提升,誠邀體驗! 一直以來不少開發者朋友在社區反饋,在使用 FIDE 工具的程序中,時常會遇到諸如加載不及時、代碼預覽/渲染性能不如意的情況,十分影響開發體驗。 作為技術團隊,我們深知一件趁手的開發工具對開發者的重要性,因此,在2023年開年,FinC ......

    uj5u.com 2023-04-19 09:14:08 more
  • 游戲內嵌社區服務開放,助力開發者提升玩家互動與留存

    華為 HMS Core 游戲內嵌社區服務提供快速訪問華為游戲中心論壇能力,支持玩家直接在游戲內瀏覽帖子和交流互動,助力開發者擴展內容生產和觸達的場景。 一、為什么要游戲內嵌社區? 二、游戲內嵌社區的典型使用場景 1、游戲內打開論壇 您可以在游戲內繪制論壇入口,為玩家提供沉浸式發帖、瀏覽、點贊、回帖、 ......

    uj5u.com 2023-04-19 09:08:34 more