主頁 > 後端開發 > JDK成長記14:(深度好文)你能從3個層面分析volatile底層原理么?(下)

JDK成長記14:(深度好文)你能從3個層面分析volatile底層原理么?(下)

2021-10-23 06:13:08 後端開發

file

上一節我們基本了解Volatile的作用,從JMM層面簡單分析了下volatile可見性的實作要求,發現JMM設定了一些操作要求,在這些要求下,可以保證執行緒間的可見性,可是具體實作是怎么實作的呢?

但是你要想理解這個實作是比較難的,之前提到按照三個層面給大家講解,如下圖所示:

file

其實上一節通過JMM分析volatile是歸于JVM層面分析的一部分而已,

你要想完全弄清楚volatile的可見性和有序性,你還要繼續分析位元組碼層面的JVM指令標記是什么?Hotspot實作的JSR記憶體屏障是什么意思?最終實作的C++代碼發出的匯編指令是什么?以及硬體層面如何實作可見性和有序性的?

所以這一節我們來繼續研究其余的部分,首先從最簡單的一個例子看起,之后手寫出一個DCL單例,通過這個例子我們來真正的弄清楚java代碼層面到JVM層面再到CPU層面的volatile原理,

讓我們開始吧!

從手寫一個DCL單例開始分析volatile

從手寫一個DCL單例開始分析volatile

在寫DCL單例前我們先簡單寫一個volatile的例子,從java代碼和位元組碼層面分析volatile底層原理,代碼如下:

public class DCLVolatile {
  volatile int i = 10;
}

你可以在IntelliJ中通過jclasslib插件(自行百度安裝)可以看到編譯后的位元組碼格式,這個volatile變數int i對應的格式如下:

file

而通常不加volatile的變數,比如int m 的位元組碼標識如下所示:

file

可以看出在java代碼層面volatile修飾的變數通過javac靜態編譯后,變成了帶有Access flags 0x0040這個特殊標記的變數,這樣之后就可以被JVM識別出來,這里是常量,如果是靜態的instance物件是0x004a,非靜態的是0x0042,

手寫DCL單例,第一步你需要應該宣告一個volatile的實體變數,(后面會將為什么是volatile的,大家不要著急),

代碼如下:

public class DCLVolatile {
   private static volatile DCLVolatile instance; //0x004a
 }

所以在這個層面你可以得到如下的一張圖:

file

接著你需要了解一個物件創建的時候的位元組碼指令,以便于之后分析指令重排序的問題,代碼如下:

public class DCLVolatile {
   /**
    * ByteCode:Access Flag 0x004a
    */
   private static volatile DCLVolatile instance;

   private DCLVolatile(){

   }

   /**
    * ByteCode:
    * 0 new #2 <org/mfm/learn/juc/volatiles/DCLVolatile>
    * 3 dup
    * 4 invokespecial #3 <org/mfm/learn/juc/volatiles/DCLVolatile.<init>>
    * 7 astore_0
    * 8 aload_0
    * 9 areturn
    * @return
    */
   public static DCLVolatile getInstance() {
     DCLVolatile instance = new DCLVolatile();
     return instance;
   }
 

從上面的代碼可以看出 DCLVolatile instance = new DCLVolatile();的位元組碼主要是如下幾行:

 0 new #2 <org/mfm/learn/juc/volatiles/DCLVolatile>
 3 dup
 4 invokespecial #3 <org/mfm/learn/juc/volatiles/DCLVolatile.<init>>
 7 astore_0

如果這幾條位元組碼實際就是JVM指令,具體意思可以查閱官方的JVM指令手冊,這里我直接用大白話給大家解釋下:

new 肯定就是創建一個物件,注意這里只是在堆中分配空間,(叫半初始化)此時instance = null,并沒有指向堆空間

dup其實就是入運算元堆疊一個變數instance,

invokespecial其實執行了初始化操作,使用instance參考指向堆分配的空間,

astore_0將一個數值從運算元堆疊存盤到區域變數表,

JVM指令 JVM除了對底層硬體記憶體模型進行了抽象,對執行CPU指令同樣進行了抽象,這樣可以更好地做到跨平臺性, 既然JVM將底層CPU執行指令的程序進行了抽象,這里我們不去細講JVM,抽象的內容大致可以概況為如下一句話: 執行class檔案的時候是通過在記憶體結構,一套復雜的入堆疊出堆疊機制執行class中的各個JVM指令,在執行指令層面,它有自己一套獨特的JVM指令集,而這寫JVM指令就是來源于我們寫好的Java代碼,

上面程序如下圖所示:

file

你可以接著完善DCL單例最終為:

 public class DCLVolatile {

    private static volatile DCLVolatile instance;

    private DCLVolatile(){

 
    }


    public static DCLVolatile getInstance() 
      if( instance == null){
        synchronized (DCLVolatile.class){
          if(instance == null){
            instance = new DCLVolatile();
         }
        }
      }

      return instance;
    }
  } 

上面這段代碼,double判斷+ synchronized+valotile這就是典型的 DCL單例,執行緒安全的,可以保證多個執行緒獲取instance是執行緒安全,且是同一個物件,synchronized是為了保證多執行緒同時創建物件的這個操作的安全性,double判斷+volotile是為了保證這個創建操作的可見性和有序性,

上面的輸出結果證明了這個是執行緒安全的單例,

你可以測驗下:

  public static void main(String[] args) {
    new Thread(()->{
      DCLVolatile instance = DCLVolatile.getInstance();
      System.out.println(instance);
    }).start();

 

    new Thread(()->{
      DCLVolatile instance = DCLVolatile.getInstance();
      System.out.println(instance);
    }).start();
  }

輸出如下:

org.mfm.learn.juc.volatiles.DCLVolatile@71219ecd

org.mfm.learn.juc.volatiles.DCLVolatile@71219ecd

上面的輸出結果證明了這個是執行緒安全的單例,

Java代碼+位元組碼層面分析:為什么會亂序?

Java代碼+位元組碼層面分析:為什么會亂序?

volatile的可見性體現:

instance == null是volatile的讀,instance = new DCLVolatile();是volatile的寫,執行緒之間是可見的,

volatile的有序性體現:

要想知道為什么它保證了有序性,需要了解為什么會有亂序、DCL中,位元組碼亂序了會怎么樣,

一個一個來看下,首先是為什么會亂序?

所有的編程語言最侄訓變成01的機器碼,讓CPU硬體可以認識,你寫的java代碼也一樣,java代碼到CPU執行指令的程序如下圖所示:

file

圖中標紅色的就是可能指令重排的地方, 因為了提高并發度和指令執行速度,CPU或者編譯器會進行指令的優化和重排,但是我們有時候不希望指令重排,打亂順序可能造成一些有序性問題,這時候就需要一些方法來控制和實作這一點了,Java中volatile關鍵字就是一種方法,

書曰重排序:是指編譯器和處理器為了優化程式性能而對指令序列進行重新排序的一種手段, 在單執行緒程式中,對存在控制依賴的操作重排序,不會改變執行結果(這也是as-if-serial語意允許對存在控制依賴的操作做重排序的原因);但在多執行緒程式中,對存在控制依賴的操作重排序,可能會改變程式的執行結果, 其實可以理解為,就是cpu為了優化代碼的執行效率,它不會按順序執行代碼,會打亂代碼的執行順序,前提是不影響單執行緒順序執行的結果,(當然了,只考慮cpu級別的重排序,還有其他的)

Java代碼+位元組碼層面分析:位元組碼亂序了會怎么樣?

Java代碼+位元組碼層面分析:位元組碼亂序了會怎么樣?

了解了為什么會亂序后,接著我們看下位元組碼亂序了會怎么樣?

回到上面的DCL單例的代碼中,上面你了解了創建一個物件的位元組碼后,你需要分析下完善后的getInstance方法位元組碼,如下:

0 getstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
3 ifnonnull 37 (+34)
6 ldc #8 <org/mfm/learn/juc/volatiles/DCLVolatile>
8 dup
9 astore_0
10 monitorenter
11 getstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
14 ifnonnull 27 (+13)
17 new #8 <org/mfm/learn/juc/volatiles/DCLVolatile>
20 dup
21 invokespecial #9 <org/mfm/learn/juc/volatiles/DCLVolatile.<init>>
24 putstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
27 aload_0
28 monitorexit
29 goto 37 (+8)
32 astore_1
33 aload_0
34 monitorexit
35 aload_1
36 athrow
37 getstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
40 areturn

你可以抓大放小,只關心創建物件的位元組碼:

  10 monitorenter
  11 getstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
  14 ifnonnull 27 (+13)
  17 new #8 <org/mfm/learn/juc/volatiles/DCLVolatile>
  20 dup
  21 invokespecial #9 <org/mfm/learn/juc/volatiles/DCLVolatile.<init>>
  24 putstatic #7 <org/mfm/learn/juc/volatiles/DCLVolatile.instance>
  27 aload_0
  28 monitorexit
  29 goto 37 (+8)
  32 astore_1
  33 aload_0
  34 monitorexit 

monitorenter是synchronized的指令,現在可以先忽略,后面我們講Synchronized的時候會詳細講解,

創建物件的位元組核心還是3步

1) 分配空間,半初始化 new

2) 之后進行賦值操作 invokespecial

3) 再之后進行參考指向物件 astore_1

大家可以想象下,如果兩個執行緒同時呼叫getInstance方法,

執行緒1獲取到sychronized的鎖,第一次創建instance的時候,如果2)3)步的指令發生了重排序,如果沒有volatile禁止重排序的話,如下代碼創建的instance就可能不是同一個物件了,

 public static DCLVolatile getInstance() {
     if( instance == null){
       synchronized (DCLVolatile.class){
         if(instance == null){
           instance = new DCLVolatile();
         }
       }
     }
     return instance;
   }

執行緒2獲取到了instance可能是一個半初始化的物件,也就是null,直接使用的話肯定會有問題,就會創建一個新的instance,不是單例了,這就是有序性造成的問題,

如下圖所示:

file

再次從JVM層面分析:JVM指令怎么執行的?

再次從JVM層面分析:JVM指令怎么執行的?

經過上面DCL單例的例子,相信你已經對java代碼到位元組碼的volatile的作用有了進一步了解,具體怎么實作可見性和有序性的根本原理呢?這還是在JVM層面實作的,所以下面,我們接著進入JVM層面來分析,

接下來你會明白上面的JVM指令具體如何執行,由誰執行,又遵循哪些規范和規則?

讓我們來一一看下,

JVM指令具體如何執行

JVM首先就是通過類加載器加載class到JVM記憶體區域,之后又通過執行引擎來執行JVM指令,

不同的過JDK版本有不同的的JVM實作,有耳熟能詳的HotSpot,有淘寶自己的JVM實作,還有J9、OpenJDK等其他的JVM實作……

但JDK1.8后,最常見的就是HotSpot的JVM的實作,它是一套主要以C++代碼為主實作的JVM虛擬機,我們就以HotSpot舉例,

上述程序如下圖所示:

file

那么,編譯好的位元組碼檔案被JVM通過類加載器加載到記憶體結構之后,會被HotSpot來進行調度和執行對應的JVM指令,

怎么執行的呢?

HotSpot是通過內部的解釋器、JIT動態編譯器(含Client(C1)編譯器、Server(C2)編譯器)來執行JVM指令,

如下圖所示:

file

HotSpot是JVM規范的一個實作,它遵循了很多JVM虛擬機規范和JSR規范,

什么是規范? 規范可以打個比喻,規范就好比插座的插槽、插頭,它們定義了2孔和3孔的間距等等,所有的廠家都得遵循這個規范,才能讓所有的插頭插入插板,只要這個插頭符合規范,可以是任何牌子,也就是任何廠商的實作,而Java領域有很多規范,一般是由一個公共組織JCP來定義的,定義的規范是JSR-XXX,這個其實也有點像java中的介面和實作類的感覺,說白了就是具體事物的抽象定義,

JVM的虛擬機規范定義了一些規則,和可見性和有序性有關的規則是happen-before 規則:要求8種情況不能亂序執行,(可以自行百度)其中有一條很重要的規則就是:

volatile變數規則:對一個變數的寫操作先行發生于后面對這個變數的讀操作volatile變數寫,再是讀,必須保證是先寫,再讀,

Java中,其中有一個JSR規范,描述了記憶體屏障相關規范:

  1. LoadLoad****屏障:對于這樣的陳述句Load1; LoadLoad; Load2,在Load2及后續讀取操作要讀取的資料被訪問前,保證Load1要讀取的資料被讀取完畢,

  2. StoreStore****屏障:對于這樣的陳述句Store1; StoreStore; Store2,在Store2及后續寫入操作執行前,保證Store1的寫入操作對其它處理器可見,

  3. LoadStore****屏障:對于這樣的陳述句Load1; LoadStore; Store2,在Store2及后續寫入操作被刷出前,保證Load1要讀取的資料被讀取完畢,

  4. StoreLoad****屏障:對于這樣的陳述句Store1; StoreLoad; Load2,在Load2及后續所有讀取操作執行前,保證Store1的寫入對所有處理器可見,

網上有很多博客講解volatile的原理,里面寫的亂七八糟的,讓人看到頭暈眼花,搞不清楚記憶體屏障,JVM指令各種關系,真心讓人看到有些累,4種記憶體屏障其實是規范定義而已,這一點大家一定要搞明白,

在volatile的JVM實作中,是這么使用屏障的,

file

上面四種記憶體屏障結合happen-before原則,其實就是一句話:

比如LoadLoadBarrier,就是表示上面一條Load指令(讀指令),下面一條Load指令,不能重排序,

那你肯定就知道了StoreLoadBarrier屏障是什么意思,就是表示上面一條Store指令(寫指令),下面一條Load指令,不能重排序,

注意,上面這些規范只是定義,類似于介面,具體怎么實作就得看HotSpot的C++代碼了, 如下圖所示:

file

Java代碼+位元組碼層面分析:位元組碼亂序了會怎么樣?

再次從JVM層面分析:HotSpot到底怎么禁止重排序的呢?

再次從JVM層面分析:HotSpot到底怎么禁止重排序的呢?

實際是通過一些C++的fense方法,生成一些匯編語言,最終轉換為機器碼,執行CPU指令,所謂的記憶體屏障實際是一條特殊的指令,要求不能換順序,

如下圖所示:

file

這里我們不去深入HotSopt原始碼,在里面也看不出來發送給CPU的指令,需要通過工具才能看出來,你可以通過JIT生成代碼反匯編工具:(HSDIS),看出來發送給CPU的匯編代碼指令,注意,匯編代碼是給人看到,實際CPU還是識別0/1的機器碼,來執行Cpu指令的,

通過HSDIS工具,可以執行得到如下JIT反匯編語言:!

file

好了到了這里,基本JVM這一層面的volatile原理,就給大家分析清楚了,可以看到,volatile最侄訓轉換為一條CPU的lock前綴指令,

從CPU層面分析:volatile底層原理

從CPU層面分析:volatile底層原理

JVM不同的實作,對發送給CPU的指令實際都一些差異,而且在歷史上,CPU實作方式也可能不同,主要有如下三種機制:

file

前一個小節提到了lock前綴指令,是最常提到的的方式,適用于所有CPU,所有CPU都支持這個指令,lock前綴指令的之前是鎖總線這個硬體的傳輸,由于性能太差,后面優化成了總線嗅探機制+MESI協議,這樣好處是可以跨平臺,沒有CPU硬體的各種限制,

據我所知,起碼OpenJDK和HotSpot是使用lock這種方式的這樣的(這個考證起來比較困難,如果這里寫的不對,歡迎各位大神指出!)

除了lock前綴指令,也可以通過一些fence指令做到可見性和有序性的保證,當然耳熟能詳的通過MESI協議也可以做到,

下面我們分別來看下這3種機制,

在了解之前,這里需要回顧下計算機的組成和CPU的硬體快取結構,之前也提到過,CPU的硬體快取結構實際是可以和JMM記憶體邏輯模型對應上的,

我們先來看下,計算機的組成如下圖:

file

再來看下CPU核心組件圖:

file

有了上面的2張圖,你就可以知道,實際CPU執行的是通過共享的記憶體:高速快取、RAM記憶體、L3,CPU內部執行緒私有的記憶體L1、L2快取,通過總線從逐層將快取讀入每一級快取,如下流程所示:

RAM記憶體->高速快取(L4一般位于總線)->L3級快取(CPU共享)->L2級快取(CPU內部私有)->L1級快取(CPU內部私有),

這樣當java中多個執行緒執行的時候,實際是交給CPU的每個暫存器執行每一個執行緒,一套暫存器+程式計數器可以執行一個執行緒,平常我們說的4核8執行緒,實際指的是8個暫存器,所以Java多執行緒執行的邏輯對應CPU組件如下圖所示:

file

當你有了上面幾張圖的概念,就可以理解指令在不同CPU和快取直接作用,

CPU硬體實作可見性和有序性3種機制

系統fence類指令

X86 CPU的可以通過fence類指令實作類似記憶體屏障的操作:

a) sfence:在sfence指令前的寫操作當必須在sfence指令后的寫操作前完成,

b) lfence:在lfence指令前的讀操作當必須在lfence指令后的讀操作前完成,

c) mfence:在mfence指令前的讀寫操作當必須在mfence指令后的讀寫操作前完成,

這種機制不太適用于所有CPU,所以目前不怎么采用了,

  • locc前綴指令

IntelCPU lock前綴匯編指令保證有序性,Lock前綴指令幾乎適用于所有CPU,

它的原子指令,如X86的Intel上,local addl XX指令是一個Full Barraier,會鎖住記憶體子系統來確保執行順序,甚至跨多個CPU,SoftwareLocks通常使用了記憶體屏障或者原子指令,來實作變數可見性和保持程式順序,

上面看上去有點難懂,大家這么理解就行:

這個指令最早的時候,其實人家用的是一個叫做總線加鎖機制,目前應該已經沒有人來用了,他大概的意思是說,某個cpu如果要讀一個資料,會通過一個總線,對這個資料加一個鎖,其他的cpu就沒法通過總線去讀和寫這個資料了,只有當這個cpu修改完了以后,其他cpu可以讀到最新的資料,

但是由于這樣多執行緒下會造成串行化,性能低,后來結合lock前綴指令+總線嗅探機制+廣為人知的MESI協議進行了優化,(這里如果說的不準確,大家可以提出來),

所以我們來具體研究下MESI到底通過哪些指令來實作,MESI的機制流程有時如何的,

MESI協議

快取一致性協議有很多,比如除了MESI之外的快取一致性協議還有MSI、MOSI、Synapse Firefly Dragon等等,

這里用的最多的就是MESI這個協議,

什么是MESI協議?

MESI協議規定:對一個共享變數的讀操作可以是多個處理器并發執行的,但是如果是對一個共享變數的寫操作,只有一個處理器可以執行,其實也會通過排他鎖的機制保證就一個處理器能寫,

要想理解這個協議需要具備兩個前提:

  1. 熟悉MESI的4個指令

  2. 熟悉CUP結構和快取行的資料結構

首先先來了解下快取行的概念:

快取行默認是64位元組Byte,(程式區域性原理,當讀取一條資料的時候,也會讀取它附近的元素,很大可能會用到)經過工業界實踐,可以充分發揮總線CPU針腳等一次性讀取資料的能力,提高效率,

一般情況,快取行的基本單位是一個64位元組的資料,用于在L1、L2、L3、高速快取Cache間傳輸資料,

處理器高速快取的底層資料結構實際是一個拉鏈散串列的結構,就是有很多個bucket,每個bucket掛了很多的cache entry,每個cache entry由三個部分組成:tag、cache line和flag,其中的cache line就是快取的資料,

tag指向了這個快取資料在主記憶體中的資料的地址,flag標識了快取行的狀態,另外要注意的一點是,cache line中可以包含多個變數的值,

file

接著再來了解下MESI的4個指令:

MESI協議規定了一組訊息,就說各個處理器在操作記憶體資料的時候,都會往總線發送訊息,而且各個處理器還會不停的從總線嗅探最新的訊息,通過這個總線的訊息傳遞來保證各個處理器的協作,

之前說過那個cache entry的flag代表了快取資料的狀態,MESI協議中劃分為:

(1)invalid:無效的,標記為I,這個意思就是當前cache entry無效,里面的資料不能使用

(2)shared:共享的,標記為S,這個意思是當前cache entry有效,而且里面的資料在各個處理器中都有各自的副本,但是這些副本的值跟主記憶體的值是一樣的,各個處理器就是并發的在讀而已

(3)exclusive:獨占的,標記為E,這個意思就是當前處理器對這個資料獨占了,只有他可以有這個副本,其他的處理器都不能包含這個副本

(4)modified:修改過的,標記為M,只能有一個處理器對共享資料更新,所以只有更新資料的處理器的cache entry,才是exclusive狀態,表明當前執行緒更新了這個資料,這個副本的資料跟主記憶體是不一樣的

到底底層是如何實作這套MESI的機制,通過哪些指令,這個指令干了什么事情,才能保證說,我剛才說的那種效果,修改本地快取,立馬刷主存,其他cpu本地快取立馬工期,重新從主存加載,

下面來詳細的圖解MESI協議的作業原理:

讀I->S

處理器0讀取某個變數的資料時,首先會根據index、tag和offset從高速快取的拉鏈散串列讀取資料,如果發現狀態為I,也就是無效的,此時就會發送read訊息到總線

接著主記憶體會回傳對應的資料給處理器0,處理器0就會把資料放到高速快取里,同時cache entry的flag狀態是S,如下圖所示:

file

CPU1:S->I->I-ack

在處理器0對一個資料進行更新的時候,如果資料狀態是S,則此時就需要發送一個invalidate訊息到總線,嘗試讓其他的處理器的高速快取的cache entry全部變為I,以獲得資料的獨占鎖,

其他的處理器1會從總線嗅探到invalidate訊息,此時就會把自己的cache entry設定為I,也就是過期掉自己本地的快取,然后就是回傳invalidate ack訊息到總線,傳遞回處理器0,處理器0必須收到所有處理器回傳的ack訊息

CPU0:S->I-ack->E->M

接著處理器0就會將cache entry先設定為E,獨占這條資料,在獨占期間,別的處理器就不能修改資料了,因為別的處理器此時發出invalidate訊息,這個處理器0是不會回傳invalidate ack訊息的,除非他先修改完再說

接著處理器0就是修改這條資料,接著將資料設定為M,也有可能是把資料此時強制寫回到主記憶體中,具體看底層硬體實作

然后其他處理器此時這條資料的狀態都是I了,那如果要讀的話,全部都需要重新發送read訊息,從主記憶體(或者是其他處理器)來加載,這個具體怎么實作要看底層的硬體了,都有可能的,

上述程序如下圖所示:

file

這套機制其實就是快取一致性在硬體快取模型下的完整的執行原理,

小結

到這里我們從三個層面,Java代碼和位元組碼->JVM層->CPU硬體原理層面,剖析了Volatile底層原理,相信大家對它的可見性、有序性深刻的理解,

這一節涉及的知識特別多,也特別燒腦,大家理解了它的原理之后,更重要的是記住它的使用場景,我給大家總結如下:

原理:

一句話簡單概括volatile的原理:就是重繪主記憶體,強制過期其他執行緒的作業記憶體,你可以在不同層面解釋:

在java代碼層面

場景:

1、 多個執行緒對同一個變數有讀有寫的時候

2、 多個執行緒需要保證有序性和可見性的時候

除了DCL單例,還有執行緒的優雅關閉這些場景,大家可以在評論去發表自己遇見過的場景,

本文由博客群發一文多發等運營工具平臺 OpenWrite 發布

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

標籤:Java

上一篇:springboot 后臺管理模塊架構設計方案

下一篇:Kettle 利用JavaScript代碼靈活決議資料

標籤雲
其他(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)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more