偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

告訴你一個(gè) AtomicInteger 的驚天大秘密!

開(kāi)發(fā) 前端
在 JDK1.5 之前,為了確保在多線(xiàn)程下對(duì)某基本數(shù)據(jù)類(lèi)型或者引用數(shù)據(jù)類(lèi)型運(yùn)算的原子性,必須依賴(lài)于外部關(guān)鍵字 synchronized,但是這種情況在 JDK1.5 之后發(fā)生了改觀(guān),當(dāng)然你依然可以使用 synchronized 來(lái)保證原子性,我們這里所說(shuō)的一種線(xiàn)程安全的方式是原子性的工具類(lèi),比如 「AtomicInteger、AtomicBoolean」 等。

[[342909]]

 i++ 不是線(xiàn)程安全的操作,因?yàn)樗皇且粋€(gè)原子性操作。

那么,如果我想要達(dá)到類(lèi)似 i++ 的這種效果,我應(yīng)該使用哪些集合或者說(shuō)工具類(lèi)呢?

在 JDK1.5 之前,為了確保在多線(xiàn)程下對(duì)某基本數(shù)據(jù)類(lèi)型或者引用數(shù)據(jù)類(lèi)型運(yùn)算的原子性,必須依賴(lài)于外部關(guān)鍵字 synchronized,但是這種情況在 JDK1.5 之后發(fā)生了改觀(guān),當(dāng)然你依然可以使用 synchronized 來(lái)保證原子性,我們這里所說(shuō)的一種線(xiàn)程安全的方式是原子性的工具類(lèi),比如 「AtomicInteger、AtomicBoolean」 等。這些原子類(lèi)都是線(xiàn)程安全的工具類(lèi),他們同時(shí)也是 Lock-Free 的。下面我們就來(lái)一起認(rèn)識(shí)一下這些工具類(lèi)以及 Lock - Free 是個(gè)什么概念。

了解 AtomicInteger

AtomicInteger 是 JDK1.5 新添加的工具類(lèi),我們首先來(lái)看一下它的繼承關(guān)系

 

與 int 的包裝類(lèi) Integer 一樣,都是繼承于 Number 類(lèi)的。

 

這個(gè) Number 類(lèi)是基本數(shù)據(jù)類(lèi)型的包裝類(lèi),一般和數(shù)據(jù)類(lèi)型有關(guān)的對(duì)象都會(huì)繼承于 Number 類(lèi)。

它的繼承體系很簡(jiǎn)單,下面我們來(lái)看一下它的基本屬性和方法

AtomicInteger 的基本屬性

AtomicInteger 的基本屬性有三個(gè)

 

Unsafe 是 sun.misc 包下面的類(lèi),AtomicInteger 主要是依賴(lài)于 sun.misc.Unsafe 提供的一些 native 方法保證操作的原子性。

Unsafe 的 objectFieldOffset 方法可以獲取成員屬性在內(nèi)存中的地址相對(duì)于對(duì)象內(nèi)存地址的偏移量。說(shuō)得簡(jiǎn)單點(diǎn)就是找到這個(gè)變量在內(nèi)存中的地址,便于后續(xù)通過(guò)內(nèi)存地址直接進(jìn)行操作,這個(gè)值就是 value這個(gè)我們后面會(huì)再細(xì)說(shuō)

value 就是 AtomicIneger 的值。

AtomicInteger 的構(gòu)造方法

繼續(xù)往下看,AtomicInteger 的構(gòu)造方法只有兩個(gè),一個(gè)是無(wú)參數(shù)的構(gòu)造方法,無(wú)參數(shù)的構(gòu)造方法默認(rèn)的 value 初始值是 0 ,帶參數(shù)的構(gòu)造方法可以指定初始值。

 

AtomicInteger 中的方法

下面我們就來(lái)聊一下 AtomicInteger 中的方法。

Get 和 Set

我們首先來(lái)看一下最簡(jiǎn)單的 get 、set 方法:

get() : 獲取當(dāng)前 AtomicInteger 的值

set() : 設(shè)置當(dāng)前 AtomicInteger 的值

get() 可以原子性的讀取 AtomicInteger 中的數(shù)據(jù),set() 可以原子性的設(shè)置當(dāng)前的值,因?yàn)?get() 和 set() 最終都是作用于 value 變量,而 value 是由 volatile 修飾的,所以 get 、set 相當(dāng)于都是對(duì)內(nèi)存進(jìn)行讀取和設(shè)置。如下圖所示

 

我們上面提到了 i++ 和 i++ 的非原子性操作,我們說(shuō)可以使用 AtomicInteger 中的方法進(jìn)行替換。

Incremental 操作

AtomicInteger 中的 Incremental 相關(guān)方法可以滿(mǎn)足我們的需求

  • getAndIncrement() : 原子性的增加當(dāng)前的值,并把結(jié)果返回。相當(dāng)于 i++的操作。

 

為了驗(yàn)證是不是線(xiàn)程安全的,我們用下面的例子進(jìn)行測(cè)試

  1. public class TAtomicTest implements Runnable{ 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(); 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         for(int i = 0;i < 10000;i++){ 
  8.             System.out.println(atomicInteger.getAndIncrement()); 
  9.         } 
  10.     } 
  11.     public static void main(String[] args) { 
  12.  
  13.         TAtomicTest tAtomicTest = new TAtomicTest(); 
  14.  
  15.         Thread t1 = new Thread(tAtomicTest); 
  16.         Thread t2 = new Thread(tAtomicTest); 
  17.         t1.start(); 
  18.         t2.start(); 
  19.     } 
  20.  

通過(guò)輸出結(jié)果你會(huì)發(fā)現(xiàn)它是一個(gè)線(xiàn)程安全的操作,你可以修改 i 的值,但是最后的結(jié)果仍然是 i - 1,因?yàn)橄热≈担缓笤?+ 1,它的示意圖如下。

 

  • incrementAndGet 與此相反,首先執(zhí)行 + 1 操作,然后返回自增后的結(jié)果,該操作方法能夠確保對(duì) value 的原子性操作。如下圖所示

 

Decremental 操作

與此相對(duì),x-- 或者 x = x - 1 這樣的自減操作也是原子性的。我們?nèi)匀豢梢允褂?AtomicInteger 中的方法來(lái)替換

  • getAndDecrement : 返回當(dāng)前類(lèi)型的 int 值,然后對(duì) value 的值進(jìn)行自減運(yùn)算。下面是測(cè)試代碼
  1. class TAtomicTestDecrement implements Runnable{ 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(20000); 
  4.  
  5.     @Override 
  6.     public void run() { 
  7.         for(int i = 0;i < 10000 ;i++){ 
  8.             System.out.println(atomicInteger.getAndDecrement()); 
  9.         } 
  10.     } 
  11.  
  12.     public static void main(String[] args) { 
  13.  
  14.         TAtomicTestDecrement tAtomicTest = new TAtomicTestDecrement(); 
  15.  
  16.         Thread t1 = new Thread(tAtomicTest); 
  17.         Thread t2 = new Thread(tAtomicTest); 
  18.         t1.start(); 
  19.         t2.start(); 
  20.  
  21.     } 
  22.  

下面是 getAndDecrement 的示意圖

 

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

 

LazySet方法

volatile 有內(nèi)存屏障你知道嗎?

內(nèi)存屏障是啥啊?

內(nèi)存屏障,也稱(chēng)內(nèi)存柵欄,內(nèi)存柵障,屏障指令等, 是一類(lèi)同步屏障指令,是 CPU 或編譯器在對(duì)內(nèi)存隨機(jī)訪(fǎng)問(wèn)的操作中的一個(gè)同步點(diǎn),使得此點(diǎn)之前的所有讀寫(xiě)操作都執(zhí)行后才可以開(kāi)始執(zhí)行此點(diǎn)之后的操作。也是一個(gè)讓CPU 處理單元中的內(nèi)存狀態(tài)對(duì)其它處理單元可見(jiàn)的一項(xiàng)技術(shù)。

CPU 使用了很多優(yōu)化,使用緩存、指令重排等,其最終的目的都是為了性能,也就是說(shuō),當(dāng)一個(gè)程序執(zhí)行時(shí),只要最終的結(jié)果是一樣的,指令是否被重排并不重要。所以指令的執(zhí)行時(shí)序并不是順序執(zhí)行的,而是亂序執(zhí)行的,這就會(huì)帶來(lái)很多問(wèn)題,這也促使著內(nèi)存屏障的出現(xiàn)。

語(yǔ)義上,內(nèi)存屏障之前的所有寫(xiě)操作都要寫(xiě)入內(nèi)存;內(nèi)存屏障之后的讀操作都可以獲得同步屏障之前的寫(xiě)操作的結(jié)果。因此,對(duì)于敏感的程序塊,寫(xiě)操作之后、讀操作之前可以插入內(nèi)存屏障。

內(nèi)存屏障的開(kāi)銷(xiāo)非常輕量級(jí),但是再小也是有開(kāi)銷(xiāo)的,LazySet 的作用正是如此,它會(huì)以普通變量的形式來(lái)讀寫(xiě)變量。

也可以說(shuō)是:「懶得設(shè)置屏障了」

 

GetAndSet 方法

以原子方式設(shè)置為給定值并返回舊值。

它的源碼就是調(diào)用了一下 unsafe 中的 getAndSetInt 方法,如下所示

 

就是先進(jìn)行循環(huán),然后調(diào)用 getIntVolatile 方法,這個(gè)方法我在 cpp 中沒(méi)有找到,找到的小伙伴們記得及時(shí)告訴讓我學(xué)習(xí)一下。

循環(huán)直到 compareAndSwapInt 返回 false,這就說(shuō)明使用 CAS 并沒(méi)有更新為新的值,所以 var5 返回的就是最新的內(nèi)存值。

CAS 方法

我們一直常說(shuō)的 CAS 其實(shí)就是 CompareAndSet 方法,這個(gè)方法顧名思義,就是 「比較并更新」 的意思,當(dāng)然這是字面理解,字面理解有點(diǎn)偏差,其實(shí)人家的意思是先比較,如果滿(mǎn)足那么再進(jìn)行更新。

 

上面給出了 CAS Java 層面的源碼,JDK 官方給它的解釋就是 「如果當(dāng)前值等于 expect 的值,那么就以原子性的方式將當(dāng)前值設(shè)置為 update 給定值」,這個(gè)方法會(huì)返回一個(gè) boolean 類(lèi)型,如果是 true 就表示比較并更新成功,否則表示失敗。

CAS 同時(shí)也是一種無(wú)鎖并發(fā)機(jī)制,也稱(chēng)為 Lock Free,所以你覺(jué)得 Lock Free 很高大上嗎?并沒(méi)有。

下面我們構(gòu)建一個(gè)加鎖解鎖的 CASLock

  1. class CASLock { 
  2.  
  3.     AtomicInteger atomicInteger = new AtomicInteger(); 
  4.     Thread currentThread = null
  5.  
  6.     public void tryLock() throws Exception{ 
  7.  
  8.         boolean isLock = atomicInteger.compareAndSet(0, 1); 
  9.         if(!isLock){ 
  10.             throw new Exception("加鎖失敗"); 
  11.         } 
  12.  
  13.         currentThread = Thread.currentThread(); 
  14.         System.out.println(currentThread + " tryLock"); 
  15.  
  16.     } 
  17.  
  18.     public void unlock() { 
  19.  
  20.         int lockValue = atomicInteger.get(); 
  21.         if(lockValue == 0){ 
  22.             return
  23.         } 
  24.         if(currentThread == Thread.currentThread()){ 
  25.             atomicInteger.compareAndSet(1,0); 
  26.             System.out.println(currentThread + " unlock"); 
  27.         } 
  28.     } 
  29.  
  30.     public static void main(String[] args) { 
  31.  
  32.         CASLock casLock = new CASLock(); 
  33.  
  34.         for(int i = 0;i < 5;i++){ 
  35.  
  36.             new Thread(() -> { 
  37.                 try { 
  38.                     casLock.tryLock(); 
  39.                     Thread.sleep(10000); 
  40.                 } catch (Exception e) { 
  41.                     e.printStackTrace(); 
  42.                 }finally { 
  43.                     casLock.unlock(); 
  44.                 } 
  45.             }).start(); 
  46.         } 
  47.  
  48.     } 

在上面的代碼中,我們構(gòu)建了一個(gè) CASLock,在 tryLock 方法中,我們先使用 CAS 方法進(jìn)行更新,如果更新不成功則拋出異常,并把當(dāng)前線(xiàn)程設(shè)置為加鎖線(xiàn)程。在 unLock 方法中,我們先判斷當(dāng)前值是否為 0 ,如果是 0 就是我們?cè)敢饪吹降慕Y(jié)果,直接返回。否則是 1,則表示當(dāng)前線(xiàn)程還在加鎖,我們?cè)賮?lái)判斷一下當(dāng)前線(xiàn)程是否是加鎖線(xiàn)程,如果是則執(zhí)行解鎖操作。

那么我們上面提到的 compareAndSet,它其實(shí)可以解析為如下操作

  1. // 偽代碼 
  2.  
  3. // 當(dāng)前值 
  4. int v = 0; 
  5. int a = 0; 
  6. int b = 1; 
  7.  
  8. if(compare(0,0) == true){ 
  9.   set(0,1); 
  10. else
  11.   // 繼續(xù)向下執(zhí)行 

也可以拿生活場(chǎng)景中的買(mǎi)票舉例子,你去景區(qū)旅游肯定要持票才能進(jìn),如果你拿著是假票或者不符合景區(qū)的票肯定是能夠被識(shí)別出來(lái)的,如果你沒(méi)有拿票拿你也肯定進(jìn)不去景區(qū)。

廢話(huà)少說(shuō),這就祭出來(lái) compareAndSet 的示意圖

 

weakCompareAndSet: 媽的非常認(rèn)真看了好幾遍,發(fā)現(xiàn) JDK1.8 的這個(gè)方法和 compareAndSet 方法完全一摸一樣啊,坑我。。。

 

但是真的是這樣么?并不是,JDK 源碼很博大精深,才不會(huì)設(shè)計(jì)一個(gè)重復(fù)的方法,你想想 JDK 團(tuán)隊(duì)也不是會(huì)犯這種低級(jí)團(tuán)隊(duì),但是原因是什么呢?

《Java 高并發(fā)詳解》這本書(shū)給出了我們一個(gè)答案

 

AddAndGet

AddAndGet 和 getAndIncrement、getAndAdd、incrementAndGet 等等方法都是使用了 do ... while + CAS 操作,其實(shí)也就相當(dāng)于是一個(gè)自旋鎖,如果 CAS 修改成功就會(huì)一直循環(huán),修改失敗才會(huì)返回。示意圖如下

 

深入 AtomicInteger

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

AtomicInteger 的底層實(shí)現(xiàn)原理我們?cè)賮?lái)瞧瞧這個(gè)可愛(ài)的 compareAndSetL(CAS) 方法,為什么就這兩行代碼就保證原子性了?

 

我們可以看到,這個(gè) CAS 方法相當(dāng)于是調(diào)用了 unsafe 中的 compareAndSwapInt 方法,我們進(jìn)到 unsafe 方能發(fā)中看一下具體實(shí)現(xiàn)。

 

compareAndSwapInt 是 sun.misc 中的方法,這個(gè)方法是一個(gè) native 方法,它的底層是 C/C++ 實(shí)現(xiàn)的,所以我們需要看 C/C++ 的源碼。

知道 C/C++ 的牛逼之處了么。使用 Java 就是玩應(yīng)用和架構(gòu)的,C/C++ 是玩服務(wù)器、底層的。

compareAndSwapInt 的源碼在 jdk8u-dev/hotspot/src/share/vm/prims/unsafe.app 路徑下,它的源碼實(shí)現(xiàn)是

 

也就是 Unsafe_CompareAndSwapInt 方法,我們找到這個(gè)方法

 

C/C++ 源碼我也看不懂,但是這不妨礙我們找到關(guān)鍵代碼 Atomic::cmpxchg ,cmpxchg 是 x86 CPU 架構(gòu)的匯編指令,它的主要作用就是比較并交換操作數(shù)。我們繼續(xù)往下跟找一下這個(gè)指令的定義。

我們會(huì)發(fā)現(xiàn)對(duì)應(yīng)不同的 os,其底層實(shí)現(xiàn)方式不一樣

 

我們找到 Windows 的實(shí)現(xiàn)方式如下

 

我們繼續(xù)向下找,它其實(shí)定義的是第 216 行的代碼,我們找進(jìn)去

 

此時(shí)就需要匯編指令和寄存器相關(guān)的知識(shí)了。

上面的 os::is-MP() 是多處理操作系統(tǒng)的接口,下面是 __asm ,它是 C/C++ 的關(guān)鍵字,用于調(diào)用內(nèi)聯(lián)匯編程序。

__asm 中的代碼是匯編程序,大致來(lái)說(shuō)就是把 dest、exchange_value 、compare_value 的值都放在寄存器中,下面的 LOCK_IF_MP 中代碼的大致意思就是

 

如果是多處理器的話(huà)就會(huì)執(zhí)行 lock,然后進(jìn)行比較操作。其中的 cmp 表示比較,mp 表示的就是 MultiProcess,je 表示相等跳轉(zhuǎn),L0 表示的是標(biāo)識(shí)位。

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

樂(lè)觀(guān)鎖

你有沒(méi)有這個(gè)疑問(wèn),為什么 AtomicInteger 可以獲取當(dāng)前值,那為什么還會(huì)出現(xiàn) expectValue 和 value 不一致的情況呢?

因?yàn)?AtomicInteger 只是一個(gè)原子性的工具類(lèi),它不具有排他性,它不像是 synchronized 或者是 lock 一樣具有互斥和排他性,還記得 AtomicInteger 中有兩個(gè)方法 get 和 set 嗎?它們只是用 volatile 修飾了一下,而 volatile 不具有原子性,所以可能會(huì)存在 expectValue 和 value 的當(dāng)前值不一致的情況,因此可能會(huì)出現(xiàn)重復(fù)修改。

針對(duì)上面這種情況的解決辦法有兩種,一種是使用 synchronized 和 lock 等類(lèi)似的加鎖機(jī)制,這種鎖具有獨(dú)占性,也就是說(shuō)同一時(shí)刻只能有一個(gè)線(xiàn)程來(lái)進(jìn)行修改,這種方式能夠保證原子性,但是相對(duì)開(kāi)銷(xiāo)比較大,這種鎖也叫做悲觀(guān)鎖。另外一種解決辦法是使用版本號(hào)或者是 CAS 方法。

「版本號(hào)」

版本號(hào)機(jī)制是在數(shù)據(jù)表中加上一個(gè) version 字段來(lái)實(shí)現(xiàn)的,表示數(shù)據(jù)被修改的次數(shù),當(dāng)執(zhí)行寫(xiě)操作并且寫(xiě)入成功后,version = version + 1,當(dāng)線(xiàn)程 A 要更新數(shù)據(jù)時(shí),在讀取數(shù)據(jù)的同時(shí)也會(huì)讀取 version 值,在提交更新時(shí),若剛才讀取到的 version 值為當(dāng)前數(shù)據(jù)庫(kù)中的 version 值相等時(shí)才更新,否則重試更新操作,直到更新成功。

「CAS 方法」

還有一種方式就是 CAS 了,我們上面用了大量的篇幅來(lái)介紹 CAS 方法,那么我們認(rèn)為你現(xiàn)在已經(jīng)對(duì)其運(yùn)行機(jī)制有一定的了解了,我們就不再闡述它的運(yùn)行機(jī)制了。

任何事情都是有利也有弊,軟件行業(yè)沒(méi)有完美的解決方案只有最優(yōu)的解決方案,所以樂(lè)觀(guān)鎖也有它的弱點(diǎn)和缺陷,那就是 ABA 問(wèn)題。

ABA 問(wèn)題

ABA 問(wèn)題說(shuō)的是,如果一個(gè)變量第一次讀取的值是 A,準(zhǔn)備好需要對(duì) A 進(jìn)行寫(xiě)操作的時(shí)候,發(fā)現(xiàn)值還是 A,那么這種情況下,能認(rèn)為 A 的值沒(méi)有被改變過(guò)嗎?可以是由 A -> B -> A 的這種情況,但是 AtomicInteger 卻不會(huì)這么認(rèn)為,它只相信它看到的,它看到的是什么就是什么。舉個(gè)例子來(lái)說(shuō)

假如現(xiàn)在有一個(gè)單鏈表,如下圖所示

 

A.next = B ,B.next = null,此時(shí)有兩個(gè)線(xiàn)程 T1 和 T2 分別從單鏈表中取出 A ,由于一些特殊原因,T2 把 A 改為 B ,然后又改為 A ,此時(shí) T1 執(zhí)行 CAS 方法,發(fā)現(xiàn)單鏈表仍然是 A ,就會(huì)執(zhí)行 CAS 方法,雖然結(jié)果沒(méi)錯(cuò),但是這種操作會(huì)造成一些潛在的問(wèn)題。

 

此時(shí)還是一個(gè)單鏈表,兩個(gè)線(xiàn)程 T1 和 T2 分別從單鏈表中取出 A ,然后 T1 把鏈表改為 ACD 如下圖所示

 

此時(shí) T2,發(fā)現(xiàn)內(nèi)存值還是 A ,就會(huì)把 A 的值嘗試替換為 B ,因?yàn)?B 的引用是 null,此時(shí)就會(huì)造成 C、D 處于游離態(tài)

 

JDK 1.5 以后的 AtomicStampedReference類(lèi)就提供了此種能力,其中的 compareAndSet 方法就是首先檢查當(dāng)前值是否等于預(yù)期值,判斷的標(biāo)準(zhǔn)就是當(dāng)前引用和郵戳分別和預(yù)期引用和郵戳相等,如果全部相等,則以原子方式設(shè)置為給定的更新值。

 

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

 

簡(jiǎn)單解釋一下就是 UnsafeWrapper 就是包裝器,換個(gè)名字而已。然后經(jīng)過(guò)一些 JNI 的處理,因?yàn)?compareAndSwapOject 比較的是引用,所以需要經(jīng)過(guò) C++ 面向?qū)ο蟮霓D(zhuǎn)換。最主要的方法是 atomic_compare_exchange_oop

 

可以看到,又出現(xiàn)了熟悉的詞匯 cmpxchg ,也就是說(shuō) compareAndSwapOject 使用的還是 cmpxchg 原子性指令,只是它經(jīng)過(guò)了一系列轉(zhuǎn)換。

后記

拋出來(lái)一個(gè)問(wèn)題,CAS 能保證變量之間的可見(jiàn)性么?為什么?

還有一個(gè)問(wèn)題,getIntVolatile 方法的 cpp 源碼在哪里?怎么找?

 本文轉(zhuǎn)載自微信公眾號(hào)「Java建設(shè)者」,可以通過(guò)以下二維碼關(guān)注。轉(zhuǎn)載本文請(qǐng)聯(lián)系Java建設(shè)者公眾號(hào)。

 

責(zé)任編輯:武曉燕 來(lái)源: Java建設(shè)者
相關(guān)推薦

2020-04-17 14:16:10

SQL數(shù)據(jù)庫(kù)HTTP

2016-05-24 10:16:23

網(wǎng)絡(luò)竊案黑客銀行入侵事件

2021-08-18 11:24:51

幣圈黑客安全顧問(wèn)

2023-11-17 22:56:47

ChatGPTAI

2018-03-20 10:46:11

2021-08-12 05:29:53

區(qū)塊鏈加密貨幣黑客

2011-07-07 09:47:33

2011-07-07 09:38:50

2016-10-19 09:00:57

漏洞郵箱秘密

2018-11-01 16:58:56

蘋(píng)果iPad ProMacBook Air

2015-09-21 14:22:43

2015-11-02 10:32:43

bat騰訊百度

2017-11-09 19:40:40

2018-08-07 09:56:56

2014-03-05 09:17:43

編程愛(ài)好

2010-05-13 00:03:44

2023-08-17 11:53:22

2011-06-30 09:37:08

JavaDB2SQL

2016-06-27 16:29:04

戴爾閃存

2018-07-05 11:17:04

華為云
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)