成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

讀了源碼,發(fā)現(xiàn) Volatile 如此重要!

開發(fā) 前端
最近卷 Eureka 源碼,發(fā)現(xiàn) volatile 在很多地方都用到了,復(fù)習(xí)一波。此文全面講解了 volatile 的用法和細(xì)節(jié),建議收藏轉(zhuǎn)發(fā)后再看。

[[430097]]

大家好,我是悟空。

最近卷 Eureka 源碼,發(fā)現(xiàn) volatile 在很多地方都用到了,復(fù)習(xí)一波。此文全面講解了 volatile 的用法和細(xì)節(jié),建議收藏轉(zhuǎn)發(fā)后再看。

一、Volatile怎么念?

看到這個單詞一直不知道怎么發(fā)音

英 [ˈvɒlətaɪl] 美 [ˈvɑːlətl]

 

adj. [化學(xué)] 揮發(fā)性的;不穩(wěn)定的;爆炸性的;反復(fù)無常的

那Java中volatile又是干啥的呢?

二、Java中volatile用來干啥?

Volatile是Java虛擬機(jī)提供的輕量級的同步機(jī)制(三大特性)

  • 保證可見性
  • 不保證原子性
  • 禁止指令重排

要理解三大特性,就必須知道Java內(nèi)存模型(JMM),那JMM又是什么呢?

三、JMM又是啥?

這是一份精心總結(jié)的Java內(nèi)存模型思維導(dǎo)圖,拿去不謝。

原理圖1-Java內(nèi)存模型

3.1 為什么需要Java內(nèi)存模型?

Why:屏蔽各種硬件和操作系統(tǒng)的內(nèi)存訪問差異

JMM是Java內(nèi)存模型,也就是Java Memory Model,簡稱JMM,本身是一種抽象的概念,實際上并不存在,它描述的是一組規(guī)則或規(guī)范,通過這組規(guī)范定義了程序中各個變量(包括實例字段,靜態(tài)字段和構(gòu)成數(shù)組對象的元素)的訪問方式。

3.2 到底什么是Java內(nèi)存模型?

  • 1.定義程序中各種變量的訪問規(guī)則
  • 2.把變量值存儲到內(nèi)存的底層細(xì)節(jié)
  • 3.從內(nèi)存中取出變量值的底層細(xì)節(jié)

3.3 Java內(nèi)存模型的兩大內(nèi)存是啥?

原理圖2-兩大內(nèi)存

  • 主內(nèi)存
    • Java堆中對象實例數(shù)據(jù)部分
    • 對應(yīng)于物理硬件的內(nèi)存
  • 工作內(nèi)存
    • Java棧中的部分區(qū)域
    • 優(yōu)先存儲于寄存器和高速緩存

3.4 Java內(nèi)存模型是怎么做的?

Java內(nèi)存模型的幾個規(guī)范:

  • 1.所有變量存儲在主內(nèi)存
  • 2.主內(nèi)存是虛擬機(jī)內(nèi)存的一部分
  • 3.每條線程有自己的工作內(nèi)存
  • 4.線程的工作內(nèi)存保存變量的主內(nèi)存副本
  • 5.線程對變量的操作必須在工作內(nèi)存中進(jìn)行
  • 6.不同線程之間無法直接訪問對方工作內(nèi)存中的變量
  • 7.線程間變量值的傳遞均需要通過主內(nèi)存來完成

由于JVM運(yùn)行程序的實體是線程,而每個線程創(chuàng)建時JVM都會為其創(chuàng)建一個工作內(nèi)存(有些地方稱為??臻g),工作內(nèi)存是每個線程的私有數(shù)據(jù)區(qū)域,而Java內(nèi)存模型中規(guī)定所有變量都存儲在主內(nèi)存,主內(nèi)存是共享內(nèi)存區(qū)域,所有線程都可以訪問,但線程對變量的操作(讀取賦值等)必須在工作內(nèi)存中進(jìn)行,首先要將變量從主內(nèi)存拷貝到自己的工作內(nèi)存空間,然后對變量進(jìn)行操作,操作完成后再將變量寫會主內(nèi)存,不能直接操作主內(nèi)存中的變量,各個線程中的工作內(nèi)存中存儲著主內(nèi)存中的變量副本拷貝,因此不同的線程間無法訪問對方的工作內(nèi)存,線程間的通信(傳值)必須通過主內(nèi)存來完成,其簡要訪問過程:

原理圖3-Java內(nèi)存模型

3.5 Java內(nèi)存模型的三大特性

可見性(當(dāng)一個線程修改了共享變量的值時,其他線程能夠立即得知這個修改)

原子性(一個操作或一系列操作是不可分割的,要么同時成功,要么同時失敗)

有序性(變量賦值操作的順序與程序代碼中的執(zhí)行順序一致)

關(guān)于有序性:如果在本線程內(nèi)觀察,所有的操作都是有序的;如果在一個線程中觀察另一個線程,所有的操作都是無序的。前半句是指“線程內(nèi)似表現(xiàn)為串行的語義”(Within-Thread As-If-Serial Semantics),后半句是指“指令重排序”現(xiàn)象和“工作內(nèi)存與主內(nèi)存同步延遲”現(xiàn)象。

四、能給個示例說下怎么用volatile的嗎?

考慮一下這種場景:

有一個對象的字段number初始化值=0,另外這個對象有一個公共方法setNumberTo100()可以設(shè)置number = 100,當(dāng)主線程通過子線程來調(diào)用setNumberTo100()后,主線程是否知道number值變了呢?

答案:如果沒有使用volatile來定義number變量,則主線程不知道子線程更新了number的值。

(1)定義如上述所說的對象:ShareData

  1. class ShareData { 
  2.     int number = 0; 
  3.  
  4.     public void setNumberTo100() { 
  5.         this.number = 100; 
  6.     } 

(2)主線程中初始化一個子線程,名字叫做子線程

子線程先休眠3s,然后設(shè)置number=100。主線程不斷檢測的number值是否等于0,如果不等于0,則退出主線程。

  1. public class volatileVisibility { 
  2.     public static void main(String[] args) { 
  3.         // 資源類 
  4.         ShareData shareData = new ShareData(); 
  5.  
  6.         // 子線程 實現(xiàn)了Runnable接口的,lambda表達(dá)式 
  7.         new Thread(() -> { 
  8.  
  9.             System.out.println(Thread.currentThread().getName() + "\t come in"); 
  10.  
  11.             // 線程睡眠3秒,假設(shè)在進(jìn)行運(yùn)算 
  12.             try { 
  13.                 TimeUnit.SECONDS.sleep(3); 
  14.             } catch (InterruptedException e) { 
  15.                 e.printStackTrace(); 
  16.             } 
  17.             // 修改number的值 
  18.             myData.setNumberTo100(); 
  19.  
  20.             // 輸出修改后的值 
  21.             System.out.println(Thread.currentThread().getName() + "\t update number value:" + myData.number); 
  22.  
  23.         }, "子線程").start(); 
  24.  
  25.         while(myData.number == 0) { 
  26.             // main線程就一直在這里等待循環(huán),直到number的值不等于零 
  27.         } 
  28.  
  29.         // 按道理這個值是不可能打印出來的,因為主線程運(yùn)行的時候,number的值為0,所以一直在循環(huán) 
  30.         // 如果能輸出這句話,說明子線程在睡眠3秒后,更新的number的值,重新寫入到主內(nèi)存,并被main線程感知到了 
  31.         System.out.println(Thread.currentThread().getName() + "\t 主線程感知到了 number 不等于 0"); 
  32.  
  33.         /** 
  34.          * 最后輸出結(jié)果: 
  35.          * 子線程     come in 
  36.          * 子線程     update number value:100 
  37.          * 最后線程沒有停止,并行沒有輸出"主線程知道了 number 不等于0"這句話,說明沒有用volatile修飾的變量,變量的更新是不可見的 
  38.          */ 
  39.     } 

沒有使用volatile

(3)我們用volatile修飾變量number

  1. class ShareData { 
  2.     //volatile 修飾的關(guān)鍵字,是為了增加多個線程之間的可見性,只要有一個線程修改了內(nèi)存中的值,其它線程也能馬上感知 
  3.     volatile int number = 0; 
  4.  
  5.     public void setNumberTo100() { 
  6.         this.number = 100; 
  7.     } 

輸出結(jié)果:

  1. 子線程  come in 
  2. 子線程  update number value:100 
  3. main  主線程知道了 number 不等于 0 
  4.  
  5. Process finished with exit code 0 

mark

「小結(jié):說明用volatile修飾的變量,當(dāng)某線程更新變量后,其他線程也能感知到。」

五、那為什么其他線程能感知到變量更新?

其實這里就是用到了“窺探(snooping)”協(xié)議。在說“窺探(snooping)”協(xié)議之前,首先談?wù)劸彺嬉恢滦缘膯栴}。

5.1 緩存一致性

當(dāng)多個CPU持有的緩存都來自同一個主內(nèi)存的拷貝,當(dāng)有其他CPU偷偷改了這個主內(nèi)存數(shù)據(jù)后,其他CPU并不知道,那拷貝的內(nèi)存將會和主內(nèi)存不一致,這就是緩存不一致。那我們?nèi)绾蝸肀WC緩存一致呢?這里就需要操作系統(tǒng)來共同制定一個同步規(guī)則來保證,而這個規(guī)則就有MESI協(xié)議。

如下圖所示,CPU2 偷偷將num修改為2,內(nèi)存中num也被修改為2,但是CPU1和CPU3并不知道num值變了。

原理圖4-緩存一致性1

5.2 MESI

當(dāng)CPU寫數(shù)據(jù)時,如果發(fā)現(xiàn)操作的變量是共享變量,即在其它CPU中也存在該變量的副本,系統(tǒng)會發(fā)出信號通知其它CPU將該內(nèi)存變量的緩存行設(shè)置為無效。如下圖所示,CPU1和CPU3 中num=1已經(jīng)失效了。

原理圖5-緩存一致性2

當(dāng)其它CPU讀取這個變量的時,發(fā)現(xiàn)自己緩存該變量的緩存行是無效的,那么它就會從內(nèi)存中重新讀取。

如下圖所示,CPU1和CPU3發(fā)現(xiàn)緩存的num值失效了,就重新從內(nèi)存讀取,num值更新為2。

原理圖6-緩存一致性3

5.3 總線嗅探

那其他CPU是怎么知道要將緩存更新為失效的呢?這里是用到了總線嗅探技術(shù)。

每個CPU不斷嗅探總線上傳播的數(shù)據(jù)來檢查自己緩存值是否過期了,如果處理器發(fā)現(xiàn)自己的緩存行對應(yīng)的內(nèi)存地址被修改,就會將當(dāng)前處理器的緩存行設(shè)置為無效狀態(tài),當(dāng)處理器對這個數(shù)據(jù)進(jìn)行修改操作的時候,會重新從內(nèi)存中把數(shù)據(jù)讀取到處理器緩存中。

原理圖7-緩存一致性4

5.4 總線風(fēng)暴

總線嗅探技術(shù)有哪些缺點?

由于MESI緩存一致性協(xié)議,需要不斷對主線進(jìn)行內(nèi)存嗅探,大量的交互會導(dǎo)致總線帶寬達(dá)到峰值。因此不要濫用volatile,可以用鎖來替代,看場景啦~

六、能演示下volatile為什么不保證原子性嗎?

原子性:一個操作或一系列操作是不可分割的,要么同時成功,要么同時失敗。

「這個定義和volatile啥關(guān)系呀,完全不能理解呀?Show me the code!」

考慮一下這種場景:

當(dāng)20個線程同時給number自增1,執(zhí)行1000次以后,number的值為多少呢?

在單線程的場景,答案是20000,如果是多線程的場景下呢?答案是可能是20000,但很多情況下都是小于20000。

示例代碼:

  1. package com.jackson0714.passjava.threads; 
  2.  
  3. /** 
  4.  演示volatile 不保證原子性 
  5.  * @create: 2020-08-13 09:53 
  6.  */ 
  7.  
  8. public class VolatileAtomicity { 
  9.     public static volatile int number = 0; 
  10.  
  11.     public static void increase() { 
  12.         number++; 
  13.     } 
  14.  
  15.     public static void main(String[] args) { 
  16.  
  17.         for (int i = 0; i < 50; i++) { 
  18.             new Thread(() -> { 
  19.                 for (int j = 0; j < 1000; j++) { 
  20.                     increase(); 
  21.                 } 
  22.             }, String.valueOf(i)).start(); 
  23.         } 
  24.  
  25.         // 當(dāng)所有累加線程都結(jié)束 
  26.         while(Thread.activeCount() > 2) { 
  27.             Thread.yield(); 
  28.         } 
  29.  
  30.         System.out.println(number); 
  31.     } 

執(zhí)行結(jié)果:第一次19144,第二次20000,第三次19378。

volatile第一次執(zhí)行結(jié)果

volatile第二次執(zhí)行結(jié)果

volatile第三次執(zhí)行結(jié)果

我們來分析一下increase()方法,通過反編譯工具javap得到如下匯編代碼:

  1. public static void increase(); 
  2.     Code: 
  3.        0: getstatic     #2                  // Field number:I 
  4.        3: iconst_1 
  5.        4: iadd 
  6.        5: putstatic     #2                  // Field number:I 
  7.        8: return 

number++其實執(zhí)行了3條指令:

getstatic:拿number的原始值 iadd:進(jìn)行加1操作 putfield:把加1后的值寫回

執(zhí)行了getstatic指令number的值取到操作棧頂時,volatile關(guān)鍵字保證了number的值在此時是正確的,但是在執(zhí)行iconst_1、iadd這些指令的時候,其他線程可能已經(jīng)把number的值改變了,而操作棧頂?shù)闹稻妥兂闪诉^期的數(shù)據(jù),所以putstatic指令執(zhí)行后就可能把較小的number值同步回主內(nèi)存之中。

總結(jié)如下:

在執(zhí)行number++這行代碼時,即使使用volatile修飾number變量,在執(zhí)行期間,還是有可能被其他線程修改,沒有保證原子性。

七、怎么保證輸出結(jié)果是20000呢?

7.1 synchronized同步代碼塊

我們可以通過使用synchronized同步代碼塊來保證原子性。從而使結(jié)果等于20000

  1. public synchronized static void increase() { 
  2.    number++; 

synchronized同步代碼塊執(zhí)行結(jié)果

但是使用synchronized太重了,會造成阻塞,只有一個線程能進(jìn)入到這個方法。我們可以使用Java并發(fā)包(JUC)中的AtomicInterger工具包。

7.2 AtomicInterger原子性操作

我們來看看AtomicInterger原子自增的方法getAndIncrement()

AtomicInterger

  1. public static AtomicInteger atomicInteger = new AtomicInteger(); 
  2.  
  3. public static void main(String[] args) { 
  4.  
  5.     for (int i = 0; i < 20; i++) { 
  6.         new Thread(() -> { 
  7.             for (int j = 0; j < 1000; j++) { 
  8.                 atomicInteger.getAndIncrement(); 
  9.             } 
  10.         }, String.valueOf(i)).start(); 
  11.     } 
  12.  
  13.     // 當(dāng)所有累加線程都結(jié)束 
  14.     while(Thread.activeCount() > 2) { 
  15.         Thread.yield(); 
  16.     } 
  17.  
  18.     System.out.println(atomicInteger); 

多次運(yùn)行的結(jié)果都是20000。

getAndIncrement的執(zhí)行結(jié)果

八、禁止指令重排又是啥?

說到指令重排就得知道為什么要重排,有哪幾種重排。

如下圖所示,指令執(zhí)行順序是按照1>2>3>4的順序,經(jīng)過重排后,執(zhí)行順序更新為指令3->4->2->1。

原理圖8-指令重排

會不會感覺到重排把指令順序都打亂了,這樣好嗎?

可以回想下小學(xué)時候的數(shù)學(xué)題:2+3-5=?,如果把運(yùn)算順序改為3-5+2=?,結(jié)果也是一樣的。所以指令重排是要保證單線程下程序結(jié)果不變的情況下做重排。

8.1 為什么要重排

計算機(jī)在執(zhí)行程序時,為了提高性能,編譯器和處理器常常會對指令做重排序。

8.2 有哪幾種重排

1.編譯器優(yōu)化重排:編譯器在不改變單線程程序語義的前提下,可以重新安排語句的執(zhí)行順序。

2.指令級的并行重排:現(xiàn)代處理器采用了指令級并行技術(shù)來將多條指令重疊執(zhí)行。如果不存在數(shù)據(jù)依賴性,處理器可以改變語句對應(yīng)機(jī)器指令的執(zhí)行順序。

3.內(nèi)存系統(tǒng)的重排:由于處理器使用緩存和讀/寫緩沖區(qū),這使得加載和存儲操作看上去可能是在亂序執(zhí)行。

原理圖9-三種重排

注意:

  • 單線程環(huán)境里面確保最終執(zhí)行結(jié)果和代碼順序的結(jié)果一致
  • 處理器在進(jìn)行重排序時,必須要考慮指令之間的數(shù)據(jù)依賴性
  • 多線程環(huán)境中線程交替執(zhí)行,由于編譯器優(yōu)化重排的存在,兩個線程中使用的變量能否保證一致性是無法確定的,結(jié)果無法預(yù)測。

8.3 舉個例子來說說多線程中的指令重排?

設(shè)想一下這種場景:定義了變量num=0和變量flag=false,線程1調(diào)用初始化函數(shù)init()執(zhí)行后,線程調(diào)用add()方法,當(dāng)另外線程判斷flag=true后,執(zhí)行num+100操作,那么我們預(yù)期的結(jié)果是num會等于101,但因為有指令重排的可能,num=1和flag=true執(zhí)行順序可能會顛倒,以至于num可能等于100

  1. public class VolatileResort { 
  2.     static int num = 0; 
  3.     static boolean flag = false
  4.     public static void init() { 
  5.         num= 1; 
  6.         flag = true
  7.     } 
  8.     public static void add() { 
  9.         if (flag) { 
  10.             num = num + 5; 
  11.             System.out.println("num:" + num); 
  12.         } 
  13.     } 
  14.     public static void main(String[] args) { 
  15.         init(); 
  16.         new Thread(() -> { 
  17.             add(); 
  18.         },"子線程").start(); 
  19.     } 

先看線程1中指令重排:

num= 1;flag = true; 的執(zhí)行順序變?yōu)閒lag=true;num = 1;,如下圖所示的時序圖

原理圖10-線程1指令重排

如果線程2 num=num+5 在線程1設(shè)置num=1之前執(zhí)行,那么線程2的num變量值為5。如下圖所示的時序圖。

原理圖11-線程2在num=1之前執(zhí)行

8.4 volatile怎么實現(xiàn)禁止指令重排?

我們使用volatile定義flag變量:

  1. static volatile boolean flag = false

「如何實現(xiàn)禁止指令重排:」

原理:在volatile生成的指令序列前后插入內(nèi)存屏障(Memory Barries)來禁止處理器重排序。

「有如下四種內(nèi)存屏障:」

四種內(nèi)存屏障

「volatile寫的場景如何插入內(nèi)存屏障:」

  • 在每個volatile寫操作的前面插入一個StoreStore屏障(寫-寫 屏障)。
  • 在每個volatile寫操作的后面插入一個StoreLoad屏障(寫-讀 屏障)。

原理圖12-volatile寫的場景如何插入內(nèi)存屏障

StoreStore屏障可以保證在volatile寫(flag賦值操作flag=true)之前,其前面的所有普通寫(num的賦值操作num=1) 操作已經(jīng)對任意處理器可見了,保障所有普通寫在volatile寫之前刷新到主內(nèi)存。

「volatile讀場景如何插入內(nèi)存屏障:」

  • 在每個volatile讀操作的后面插入一個LoadLoad屏障(讀-讀 屏障)。
  • 在每個volatile讀操作的后面插入一個LoadStore屏障(讀-寫 屏障)。

原理圖13-volatile讀場景如何插入內(nèi)存屏障

LoadStore屏障可以保證其后面的所有普通寫(num的賦值操作num=num+5) 操作必須在volatile讀(if(flag))之后執(zhí)行。

十、volatile常見應(yīng)用

這里舉一個應(yīng)用,雙重檢測鎖定的單例模式

  1. package com.jackson0714.passjava.threads; 
  2. /** 
  3.  演示volatile 單例模式應(yīng)用(雙邊檢測) 
  4.  * @author: 悟空聊架構(gòu) 
  5.  * @create: 2020-08-17 
  6.  */ 
  7.  
  8. class VolatileSingleton { 
  9.     private static VolatileSingleton instance = null
  10.     private VolatileSingleton() { 
  11.         System.out.println(Thread.currentThread().getName() + "\t 我是構(gòu)造方法SingletonDemo"); 
  12.     } 
  13.     public static VolatileSingleton getInstance() { 
  14.         // 第一重檢測 
  15.         if(instance == null) { 
  16.             // 鎖定代碼塊 
  17.             synchronized (VolatileSingleton.class) { 
  18.                 // 第二重檢測 
  19.                 if(instance == null) { 
  20.                     // 實例化對象 
  21.                     instance = new VolatileSingleton(); 
  22.                 } 
  23.             } 
  24.         } 
  25.         return instance; 
  26.     } 

代碼看起來沒有問題,但是 instance = new VolatileSingleton();其實可以看作三條偽代碼:

  1. memory = allocate(); // 1、分配對象內(nèi)存空間 
  2. instance(memory); // 2、初始化對象 
  3. instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時instance != null 

步驟2 和 步驟3之間不存在 數(shù)據(jù)依賴關(guān)系,而且無論重排前 還是重排后,程序的執(zhí)行結(jié)果在單線程中并沒有改變,因此這種重排優(yōu)化是允許的。

  1. memory = allocate(); // 1、分配對象內(nèi)存空間 
  2. instance = memory; // 3、設(shè)置instance指向剛剛分配的內(nèi)存地址,此時instance != null,但是對象還沒有初始化完成 
  3. instance(memory); // 2、初始化對象 

如果另外一個線程執(zhí)行:if(instance == null)時,則返回剛剛分配的內(nèi)存地址,但是對象還沒有初始化完成,拿到的instance是個假的。如下圖所示:

原理圖14-雙重檢鎖存在的并發(fā)問題

解決方案:定義instance為volatile變量

  1. private static volatile VolatileSingleton instance = null

十一、volatile都不保證原子性,為啥我們還要用它?

奇怪的是,volatile都不保證原子性,為啥我們還要用它?

volatile是輕量級的同步機(jī)制,對性能的影響比synchronized小。

典型的用法:檢查某個狀態(tài)標(biāo)記以判斷是否退出循環(huán)。

比如線程試圖通過類似于數(shù)綿羊的傳統(tǒng)方法進(jìn)入休眠狀態(tài),為了使這個示例能正確執(zhí)行,asleep必須為volatile變量。否則,當(dāng)asleep被另一個線程修改時,執(zhí)行判斷的線程卻發(fā)現(xiàn)不了。

「那為什么我們不直接用synchorized,lock鎖?它們既可以保證可見性,又可以保證原子性為何不用呢?」

因為synchorized和lock是排他鎖(悲觀鎖),如果有多個線程需要訪問這個變量,將會發(fā)生競爭,只有一個線程可以訪問這個變量,其他線程被阻塞了,會影響程序的性能。

注意:當(dāng)且僅當(dāng)滿足以下所有條件時,才應(yīng)該用volatile變量

對變量的寫入操作不依賴變量的當(dāng)前值,或者你能確保只有單個線程更新變量的值。

該變量不會與其他的狀態(tài)一起納入不變性條件中。

在訪問變量時不需要加鎖。

十二、volatile和synchronzied的區(qū)別

  • volatile只能修飾實例變量和類變量,synchronized可以修飾方法和代碼塊。
  • volatile不保證原子性,而synchronized保證原子性
  • volatile 不會造成阻塞,而synchronized可能會造成阻塞
  • volatile 輕量級鎖,synchronized重量級鎖
  • volatile 和synchronized都保證了可見性和有序性

十三、小結(jié)

volatile 保證了可見性:當(dāng)一個線程修改了共享變量的值時,其他線程能夠立即得知這個修改。

volatile 保證了單線程下指令不重排:通過插入內(nèi)存屏障保證指令執(zhí)行順序。

volatitle不保證原子性,如a++這種自增操作是有并發(fā)風(fēng)險的,比如扣減庫存、發(fā)放優(yōu)惠券的場景。

volatile 類型的64位的long型和double型變量,對該變量的讀/寫具有原子性。

volatile 可以用在雙重檢鎖的單例模式中,比synchronized性能更好。

volatile 可以用在檢查某個狀態(tài)標(biāo)記以判斷是否退出循環(huán)。

代碼已提交到github/碼云:https://gitee.com/jayh2018/PassJava-Learning

參考資料:

《深入理解Java虛擬機(jī)》

《Java并發(fā)編程的藝術(shù)》

《Java并發(fā)編程實戰(zhàn)》

本文轉(zhuǎn)載自微信公眾號「悟空聊架構(gòu)」,可以通過以下二維碼關(guān)注。轉(zhuǎn)載本文請聯(lián)系悟空聊架構(gòu)公眾號。

 

責(zé)任編輯:武曉燕 來源: 悟空聊架構(gòu)
相關(guān)推薦

2022-05-06 17:34:27

安全代碼軟件漏洞

2024-03-22 11:27:54

電纜管理數(shù)據(jù)中心

2020-11-05 10:50:09

物聯(lián)網(wǎng)數(shù)據(jù)技術(shù)

2021-01-26 16:21:46

邊緣計算5GIoT

2021-09-06 09:56:10

人工智能AIAI 芯片

2024-09-18 05:30:00

GPU內(nèi)存人工智能

2023-08-01 07:31:22

2021-10-26 10:12:04

技術(shù)債務(wù)軟件開發(fā)應(yīng)用程序

2022-11-21 18:02:04

前端測試

2018-01-24 06:47:37

物聯(lián)網(wǎng)開源操作系統(tǒng)

2020-04-21 11:03:34

微服務(wù)數(shù)據(jù)工具

2023-04-10 15:41:35

2018-04-24 15:53:52

2016-10-19 14:15:45

2021-09-30 10:19:29

物聯(lián)網(wǎng)安全物聯(lián)網(wǎng)IOT

2011-12-01 10:55:16

超級計算機(jī)高性能計算Top500

2021-08-30 14:23:41

身份驗證隱私管理網(wǎng)絡(luò)安全

2025-01-13 07:33:47

2013-07-16 09:31:11

2010-06-01 10:11:01

BMCBSMIT管理
點贊
收藏

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

主站蜘蛛池模板: 日韩欧美在线观看视频网站 | 国产成人久久精品 | 欧美在线a | 久久精品91久久久久久再现 | 久久伊人亚洲 | 精品一区视频 | 午夜影院在线观看免费 | 欧美三区在线观看 | 红色av社区 | 欧美性受xxxx白人性爽 | 天天激情综合 | 国产精品久久久久久久白浊 | 欧美日韩中文字幕在线 | 美女视频一区二区三区 | 成人欧美日韩一区二区三区 | 国产日韩91 | 欧美极品在线视频 | 四虎网站在线观看 | 国产精品久久久久久久久久三级 | 天天干天天玩天天操 | 青青草综合网 | 精品国偷自产在线 | 成人一区二| 午夜爱爱毛片xxxx视频免费看 | 黄色精品 | 亚洲精品一区二区 | 国产精品欧美一区二区三区不卡 | 美女视频网站久久 | 午夜影院在线观看版 | 亚洲国产精品久久久久婷婷老年 | 性国产丰满麻豆videosex | 国产精品毛片久久久久久 | 午夜成人免费电影 | 国产精品亚洲一区二区三区在线观看 | 中文字幕第一页在线 | 一区二区在线不卡 | 九九热精品免费 | 9久久精品 | 久久国产三级 | 久久出精品 | 成人性视频在线 |