就是要你懂Java中volatile關鍵字實現原理

来源:http://www.cnblogs.com/xrq730/archive/2017/06/20/7048693.html
-Advertisement-
Play Games

原文地址http://www.cnblogs.com/xrq730/p/7048693.html,轉載請註明出處,謝謝 前言 我們知道volatile關鍵字的作用是保證變數在多線程之間的可見性,它是java.util.concurrent包的核心,沒有volatile就沒有這麼多的併發類給我們使用。 ...


原文地址http://www.cnblogs.com/xrq730/p/7048693.html,轉載請註明出處,謝謝

 

前言

我們知道volatile關鍵字的作用是保證變數在多線程之間的可見性,它是java.util.concurrent包的核心,沒有volatile就沒有這麼多的併發類給我們使用。

本文詳細解讀一下volatile關鍵字如何保證變數在多線程之間的可見性,在此之前,有必要講解一下CPU緩存的相關知識,掌握這部分知識一定會讓我們更好地理解volatile的原理,從而更好、更正確地地使用volatile關鍵字。

 

CPU緩存

CPU緩存的出現主要是為瞭解決CPU運算速度與記憶體讀寫速度不匹配的矛盾,因為CPU運算速度要比記憶體讀寫速度快得多,舉個例子:

  • 一次主記憶體的訪問通常在幾十到幾百個時鐘周期
  • 一次L1高速緩存的讀寫只需要1~2個時鐘周期
  • 一次L2高速緩存的讀寫也只需要數十個時鐘周期

這種訪問速度的顯著差異,導致CPU可能會花費很長時間等待數據到來或把數據寫入記憶體。

基於此,現在CPU大多數情況下讀寫都不會直接訪問記憶體(CPU都沒有連接到記憶體的管腳),取而代之的是CPU緩存,CPU緩存是位於CPU與記憶體之間的臨時存儲器,它的容量比記憶體小得多但是交換速度卻比記憶體快得多。而緩存中的數據是記憶體中的一小部分數據,但這一小部分是短時間內CPU即將訪問的,當CPU調用大量數據時,就可先從緩存中讀取,從而加快讀取速度。

按照讀取順序與CPU結合的緊密程度,CPU緩存可分為:

  • 一級緩存:簡稱L1 Cache,位於CPU內核的旁邊,是與CPU結合最為緊密的CPU緩存
  • 二級緩存:簡稱L2 Cache,分內部和外部兩種晶元,內部晶元二級緩存運行速度與主頻相同,外部晶元二級緩存運行速度則只有主頻的一半
  • 三級緩存:簡稱L3 Cache,部分高端CPU才有

每一級緩存中所存儲的數據全部都是下一級緩存中的一部分,這三種緩存的技術難度和製造成本是相對遞減的,所以其容量也相對遞增。

當CPU要讀取一個數據時,首先從一級緩存中查找,如果沒有再從二級緩存中查找,如果還是沒有再從三級緩存中或記憶體中查找。一般來說每級緩存的命中率大概都有80%左右,也就是說全部數據量的80%都可以在一級緩存中找到,只剩下20%的總數據量才需要從二級緩存、三級緩存或記憶體中讀取。

 

使用CPU緩存帶來的問題

用一張圖表示一下CPU-->CPU緩存-->主記憶體數據讀取之間的關係:

當系統運行時,CPU執行計算的過程如下:

  1. 程式以及數據被載入到主記憶體
  2. 指令和數據被載入到CPU緩存
  3. CPU執行指令,把結果寫到高速緩存
  4. 高速緩存中的數據寫回主記憶體

如果伺服器是單核CPU,那麼這些步驟不會有任何的問題,但是如果伺服器是多核CPU,那麼問題來了,以Intel Core i7處理器的高速緩存概念模型為例(圖片摘自《深入理解電腦系統》):

試想下麵一種情況:

  1. 核0讀取了一個位元組,根據局部性原理,它相鄰的位元組同樣被被讀入核0的緩存
  2. 核3做了上面同樣的工作,這樣核0與核3的緩存擁有同樣的數據
  3. 核0修改了那個位元組,被修改後,那個位元組被寫回核0的緩存,但是該信息並沒有寫回主存
  4. 核3訪問該位元組,由於核0並未將數據寫回主存,數據不同步

為瞭解決這個問題,CPU製造商制定了一個規則:當一個CPU修改緩存中的位元組時,伺服器中其他CPU會被通知,它們的緩存將視為無效。於是,在上面的情況下,核3發現自己的緩存中數據已無效,核0將立即把自己的數據寫回主存,然後核3重新讀取該數據。

 

反彙編Java位元組碼,查看彙編層面對volatile關鍵字做了什麼

有了上面的理論基礎,我們可以研究volatile關鍵字到底是如何實現的。首先寫一段簡單的代碼:

 1 /**
 2  * @author 五月的倉頡http://www.cnblogs.com/xrq730/p/7048693.html
 3  */
 4 public class LazySingleton {
 5 
 6     private static volatile LazySingleton instance = null;
 7     
 8     public static LazySingleton getInstance() {
 9         if (instance == null) {
10             instance = new LazySingleton();
11         }
12         
13         return instance;
14     }
15     
16     public static void main(String[] args) {
17         LazySingleton.getInstance();
18     }
19     
20 }

首先反編譯一下這段代碼的.class文件,看一下生成的位元組碼:

沒有任何特別的。要知道,位元組碼指令,比如上圖的getstatic、ifnonnull、new等,最終對應到操作系統的層面,都是轉換為一條一條指令去執行,我們使用的PC機、應用伺服器的CPU架構通常都是IA-32架構的,這種架構採用的指令集是CISC(複雜指令集),而彙編語言則是這種指令集的助記符。

因此,既然在位元組碼層面我們看不出什麼端倪,那下麵就看看將代碼轉換為彙編指令能看出什麼端倪。Windows上要看到以上代碼對應的彙編碼不難(吐槽一句,說說不難,為了這個問題我找遍了各種資料,差點就準備安裝虛擬機,在Linux系統上搞了),訪問hsdis工具路徑可直接下載hsdis工具,下載完畢之後解壓,將hsdis-amd64.dll與hsdis-amd64.lib兩個文件放在%JAVA_HOME%\jre\bin\server路徑下即可,如下圖:

然後跑main函數,跑main函數之前,加入如下虛擬機參數:

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*LazySingleton.getInstance

運行main函數即可,代碼生成的彙編指令為:

 1 Java HotSpot(TM) 64-Bit Server VM warning: PrintAssembly is enabled; turning on DebugNonSafepoints to gain additional output
 2 CompilerOracle: compileonly *LazySingleton.getInstance
 3 Loaded disassembler from D:\JDK\jre\bin\server\hsdis-amd64.dll
 4 Decoding compiled method 0x0000000002931150:
 5 Code:
 6 Argument 0 is unknown.RIP: 0x29312a0 Code size: 0x00000108
 7 [Disassembling for mach='amd64']
 8 [Entry Point]
 9 [Verified Entry Point]
10 [Constants]
11   # {method} 'getInstance' '()Lorg/xrq/test/design/singleton/LazySingleton;' in 'org/xrq/test/design/singleton/LazySingleton'
12   #           [sp+0x20]  (sp of caller)
13   0x00000000029312a0: mov     dword ptr [rsp+0ffffffffffffa000h],eax
14   0x00000000029312a7: push    rbp
15   0x00000000029312a8: sub     rsp,10h           ;*synchronization entry
16                                                 ; - org.xrq.test.design.singleton.LazySingleton::getInstance@-1 (line 13)
17   0x00000000029312ac: mov     r10,7ada9e428h    ;   {oop(a 'java/lang/Class' = 'org/xrq/test/design/singleton/LazySingleton')}
18   0x00000000029312b6: mov     r11d,dword ptr [r10+58h]
19                                                 ;*getstatic instance
20                                                 ; - org.xrq.test.design.singleton.LazySingleton::getInstance@0 (line 13)
21   0x00000000029312ba: test    r11d,r11d
22   0x00000000029312bd: je      29312e0h
23   0x00000000029312bf: mov     r10,7ada9e428h    ;   {oop(a 'java/lang/Class' = 'org/xrq/test/design/singleton/LazySingleton')}
24   0x00000000029312c9: mov     r11d,dword ptr [r10+58h]
25   0x00000000029312cd: mov     rax,r11
26   0x00000000029312d0: shl     rax,3h            ;*getstatic instance
27                                                 ; - org.xrq.test.design.singleton.LazySingleton::getInstance@16 (line 17)
28   0x00000000029312d4: add     rsp,10h
29   0x00000000029312d8: pop     rbp
30   0x00000000029312d9: test    dword ptr [330000h],eax  ;   {poll_return}
31   0x00000000029312df: ret
32   0x00000000029312e0: mov     rax,qword ptr [r15+60h]
33   0x00000000029312e4: mov     r10,rax
34   0x00000000029312e7: add     r10,10h
35   0x00000000029312eb: cmp     r10,qword ptr [r15+70h]
36   0x00000000029312ef: jnb     293135bh
37   0x00000000029312f1: mov     qword ptr [r15+60h],r10
38   0x00000000029312f5: prefetchnta byte ptr [r10+0c0h]
39   0x00000000029312fd: mov     r11d,0e07d00b2h   ;   {oop('org/xrq/test/design/singleton/LazySingleton')}
40   0x0000000002931303: mov     r10,qword ptr [r12+r11*8+0b0h]
41   0x000000000293130b: mov     qword ptr [rax],r10
42   0x000000000293130e: mov     dword ptr [rax+8h],0e07d00b2h
43                                                 ;   {oop('org/xrq/test/design/singleton/LazySingleton')}
44   0x0000000002931315: mov     dword ptr [rax+0ch],r12d
45   0x0000000002931319: mov     rbp,rax           ;*new  ; - org.xrq.test.design.singleton.LazySingleton::getInstance@6 (line 14)
46   0x000000000293131c: mov     rdx,rbp
47   0x000000000293131f: call    2907c60h          ; OopMap{rbp=Oop off=132}
48                                                 ;*invokespecial <init>
49                                                 ; - org.xrq.test.design.singleton.LazySingleton::getInstance@10 (line 14)
50                                                 ;   {optimized virtual_call}
51   0x0000000002931324: mov     r10,rbp
52   0x0000000002931327: shr     r10,3h
53   0x000000000293132b: mov     r11,7ada9e428h    ;   {oop(a 'java/lang/Class' = 'org/xrq/test/design/singleton/LazySingleton')}
54   0x0000000002931335: mov     dword ptr [r11+58h],r10d
55   0x0000000002931339: mov     r10,7ada9e428h    ;   {oop(a 'java/lang/Class' = 'org/xrq/test/design/singleton/LazySingleton')}
56   0x0000000002931343: shr     r10,9h
57   0x0000000002931347: mov     r11d,20b2000h
58   0x000000000293134d: mov     byte ptr [r11+r10],r12l
59   0x0000000002931351: lock add dword ptr [rsp],0h  ;*putstatic instance
60                                                 ; - org.xrq.test.design.singleton.LazySingleton::getInstance@13 (line 14)
61   0x0000000002931356: jmp     29312bfh
62   0x000000000293135b: mov     rdx,703e80590h    ;   {oop('org/xrq/test/design/singleton/LazySingleton')}
63   0x0000000002931365: nop
64   0x0000000002931367: call    292fbe0h          ; OopMap{off=204}
65                                                 ;*new  ; - org.xrq.test.design.singleton.LazySingleton::getInstance@6 (line 14)
66                                                 ;   {runtime_call}
67   0x000000000293136c: jmp     2931319h
68   0x000000000293136e: mov     rdx,rax
69   0x0000000002931371: jmp     2931376h
70   0x0000000002931373: mov     rdx,rax           ;*new  ; - org.xrq.test.design.singleton.LazySingleton::getInstance@6 (line 14)
71   0x0000000002931376: add     rsp,10h
72   0x000000000293137a: pop     rbp
73   0x000000000293137b: jmp     2932b20h          ;   {runtime_call}
74 [Stub Code]
75   0x0000000002931380: mov     rbx,0h            ;   {no_reloc}
76   0x000000000293138a: jmp     293138ah          ;   {runtime_call}
77 [Exception Handler]
78   0x000000000293138f: jmp     292fca0h          ;   {runtime_call}
79 [Deopt Handler Code]
80   0x0000000002931394: call    2931399h
81   0x0000000002931399: sub     qword ptr [rsp],5h
82   0x000000000293139e: jmp     2909000h          ;   {runtime_call}
83   0x00000000029313a3: hlt
84   0x00000000029313a4: hlt
85   0x00000000029313a5: hlt
86   0x00000000029313a6: hlt
87   0x00000000029313a7: hlt

這麼長長的彙編代碼,可能大家不知道CPU在哪裡做了手腳,沒事不難,定位到59、60兩行:

0x0000000002931351: lock add dword ptr [rsp],0h  ;*putstatic instance
                                                ; - org.xrq.test.design.singleton.LazySingleton::getInstance@13 (line 14)

之所以定位到這兩行是因為這裡結尾寫明瞭line 14,line 14即volatile變數instance賦值的地方。後面的add dword ptr [rsp],0h都是正常的彙編語句,意思是將雙位元組的棧指針寄存器+0,這裡的關鍵就是add前面的lock指令,後面詳細分析一下lock指令的作用和為什麼加上lock指令後就能保證volatile關鍵字的記憶體可見性。

 

lock指令做了什麼

之前有說過IA-32架構,關於CPU架構的問題大家有興趣的可以自己查詢一下,這裡查詢一下IA-32手冊關於lock指令的描述,沒有IA-32手冊的可以去這個地址下載IA-32手冊下載地址,是個中文版本的手冊。

我摘抄一下IA-32手冊中關於lock指令作用的一些描述(因為lock指令的作用在手冊中散落在各處,並不是在某一章或者某一節專門講): 

在修改記憶體操作時,使用LOCK首碼去調用加鎖的讀-修改-寫操作,這種機制用於多處理器系統中處理器之間進行可靠的通訊,具體描述如下:
(1)在Pentium和早期的IA-32處理器中,LOCK首碼會使處理器執行當前指令時產生一個LOCK#信號,這種總是引起顯式匯流排鎖定出現
(2)在Pentium4、Inter Xeon和P6系列處理器中,加鎖操作是由高速緩存鎖或匯流排鎖來處理。如果記憶體訪問有高速緩存且隻影響一個單獨的高速緩存行,那麼操作中就會調用高速緩存鎖,而系統匯流排和系統記憶體中的實際區域內不會被鎖定。同時,這條匯流排上的其它Pentium4、Intel Xeon或者P6系列處理器就回寫所有已修改的數據並使它們的高速緩存失效,以保證系統記憶體的一致性。如果記憶體訪問沒有高速緩存且/或它跨越了高速緩存行的邊界,那麼這個處理器就會產生LOCK#信號,併在鎖定操作期間不會響應匯流排控制請求
32位IA-32處理器支持對系統記憶體中的某個區域進行加鎖的原子操作。這些操作常用來管理共用的數據結構(如信號量、段描述符、系統段或頁表),兩個或多個處理器可能同時會修改這些數據結構中的同一數據域或標誌。處理器使用三個相互依賴的機制來實現加鎖的原子操作:
1、保證原子操作
2、匯流排加鎖,使用LOCK#信號和LOCK指令首碼
3、高速緩存相干性協議,確保對高速緩存中的數據結構執行原子操作(高速緩存鎖)。這種機制存在於Pentium4、Intel Xeon和P6系列處理器中
IA-32處理器提供有一個LOCK#信號,會在某些關鍵記憶體操作期間被自動激活,去鎖定系統匯流排。當這個輸出信號發出的時候,來自其他處理器或匯流排代理的控制請求將被阻塞。軟體能夠通過預先在指令前添加LOCK首碼來指定需要LOCK語義的其它場合。
在Intel386、Intel486、Pentium處理器中,明確地對指令加鎖會導致LOCK#信號的產生。由硬體設計人員來保證系統硬體中LOCK#信號的可用性,以控制處理器間的記憶體訪問。
對於Pentinum4、Intel Xeon以及P6系列處理器,如果被訪問的記憶體區域是在處理器內部進行高速緩存的,那麼通常不發出LOCK#信號;相反,加鎖只應用於處理器的高速緩存。
為顯式地強制執行LOCK語義,軟體可以在下列指令修改記憶體區域時使用LOCK首碼。當LOCK首碼被置於其它指令之前或者指令沒有對記憶體進行寫操作(也就是說目標操作數在寄存器中)時,會產生一個非法操作碼異常(#UD)。
【1】位測試和修改指令(BTS、BTR、BTC)
【2】交換指令(XADD、CMPXCHG、CMPXCHG8B)
【3】自動假設有LOCK首碼的XCHG指令
【4】下列單操作數的算數和邏輯指令:INC、DEC、NOT、NEG
【5】下列雙操作數的算數和邏輯指令:ADD、ADC、SUB、SBB、AND、OR、XOR
一個加鎖的指令會保證對目標操作數所在的記憶體區域加鎖,但是系統可能會將鎖定區域解釋得稍大一些。
軟體應該使用相同的地址和操作數長度來訪問信號量(用作處理器之間發送信號的共用記憶體)。例如,如果一個處理器使用一個字來訪問信號量,其它處理器就不應該使用一個位元組來訪問這個信號量。
匯流排鎖的完整性不收記憶體區域對齊的影響。加鎖語義會一直持續,以滿足更新整個操作數所需的匯流排周期個數。但是,建議加鎖訪問應該對齊在它們的自然邊界上,以提升系統性能:
【1】任何8位訪問的邊界(加鎖或不加鎖)
【2】鎖定的字訪問的16位邊界
【3】鎖定的雙字訪問的32位邊界
【4】鎖定的四字訪問的64位邊界
對所有其它的記憶體操作和所有可見的外部事件來說,加鎖的操作都是原子的。所有取指令和頁表操作能夠越過加鎖的指令。加鎖的指令可用於同步一個處理器寫數據而另一個處理器讀數據的操作。
IA-32架構提供了幾種機制用來強化或弱化記憶體排序模型,以處理特殊的編程情形。這些機制包括:
【1】I/O指令、加鎖指令、LOCK首碼以及串列化指令等,強制在處理器上進行較強的排序
【2】SFENCE指令(在Pentium III中引入)和LFENCE指令、MFENCE指令(在Pentium4和Intel Xeon處理器中引入)提供了某些特殊類型記憶體操作的排序和串列化功能
...(這裡還有兩條就不寫了)
這些機制可以通過下麵的方式使用。
匯流排上的記憶體映射設備和其它I/O設備通常對向它們緩衝區寫操作的順序很敏感,I/O指令(IN指令和OUT指令)以下麵的方式對這種訪問執行強寫操作的排序。在執行了一條I/O指令之前,處理器等待之前的所有指令執行完畢以及所有的緩衝區都被都被寫入了記憶體。只有取指令和頁表查詢能夠越過I/O指令,後續指令要等到I/O指令執行完畢才開始執行。

反覆思考IA-32手冊對lock指令作用的這幾段描述,可以得出lock指令的幾個作用:

  1. 鎖匯流排,其它CPU對記憶體的讀寫請求都會被阻塞,直到鎖釋放,不過實際後來的處理器都採用鎖緩存替代鎖匯流排,因為鎖匯流排的開銷比較大,鎖匯流排期間其他CPU沒法訪問記憶體
  2. lock後的寫操作會回寫已修改的數據,同時讓其它CPU相關緩存行失效,從而重新從主存中載入最新的數據
  3. 不是記憶體屏障卻能完成類似記憶體屏障的功能,阻止屏障兩遍的指令重排序

(1)中寫了由於效率問題,實際後來的處理器都採用鎖緩存來替代鎖匯流排,這種場景下多緩存的數據一致是通過緩存一致性協議來保證的,我們來看一下什麼是緩存一致性協議。 

 

緩存一致性協議

講緩存一致性之前,先說一下緩存行的概念:

  • 緩存是分段(line)的,一個段對應一塊存儲空間,我們稱之為緩存行,它是CPU緩存中可分配的最小存儲單元,大小32位元組、64位元組、128位元組不等,這與CPU架構有關。當CPU看到一條讀取記憶體的指令時,它會把記憶體地址傳遞給一級數據緩存,一級數據緩存會檢查它是否有這個記憶體地址對應的緩存段,如果沒有就把整個緩存段從記憶體(或更高一級的緩存)中載入進來。註意,這裡說的是一次載入整個緩存段,這就是上面提過的局部性原理

上面說了,LOCK#會鎖匯流排,實際上這不現實,因為鎖匯流排效率太低了。因此最好能做到:使用多組緩存,但是它們的行為看起來只有一組緩存那樣。緩存一致性協議就是為了做到這一點而設計的,就像名稱所暗示的那樣,這類協議就是要使多組緩存的內容保持一致

緩存一致性協議有多種,但是日常處理的大多數電腦設備都屬於"嗅探(snooping)"協議,它的基本思想是:

所有記憶體的傳輸都發生在一條共用的匯流排上,而所有的處理器都能看到這條匯流排:緩存本身是獨立的,但是記憶體是共用資源,所有的記憶體訪問都要經過仲裁(同一個指令周期中,只有一個CPU緩存可以讀寫記憶體)。

CPU緩存不僅僅在做記憶體傳輸的時候才與匯流排打交道,而是不停在嗅探匯流排上發生的數據交換,跟蹤其他緩存在做什麼。所以當一個緩存代表它所屬的處理器去讀寫記憶體時,其它處理器都會得到通知,它們以此來使自己的緩存保持同步。只要某個處理器一寫記憶體,其它處理器馬上知道這塊記憶體在它們的緩存段中已失效。

MESI協議是當前最主流的緩存一致性協議,在MESI協議中,每個緩存行有4個狀態,可用2個bit表示,它們分別是:

這裡的I、S和M狀態已經有了對應的概念:失效/未載入、乾凈以及髒的緩存段。所以這裡新的知識點只有E狀態,代表獨占式訪問,這個狀態解決了"在我們開始修改某塊記憶體之前,我們需要告訴其它處理器"這一問題:只有當緩存行處於E或者M狀態時,處理器才能去寫它,也就是說只有在這兩種狀態下,處理器是獨占這個緩存行的。當處理器想寫某個緩存行時,如果它沒有獨占權,它必須先發送一條"我要獨占權"的請求給匯流排,這會通知其它處理器把它們擁有的同一緩存段的拷貝失效(如果有)。只有在獲得獨占權後,處理器才能開始修改數據----並且此時這個處理器知道,這個緩存行只有一份拷貝,在我自己的緩存里,所以不會有任何衝突。

反之,如果有其它處理器想讀取這個緩存行(馬上能知道,因為一直在嗅探匯流排),獨占或已修改的緩存行必須先回到"共用"狀態。如果是已修改的緩存行,那麼還要先把內容回寫到記憶體中。

 

由lock指令回看volatile變數讀寫

相信有了上面對於lock的解釋,volatile關鍵字的實現原理應該是一目瞭然了。首先看一張圖:

工作記憶體Work Memory其實就是對CPU寄存器和高速緩存的抽象,或者說每個線程的工作記憶體也可以簡單理解為CPU寄存器和高速緩存。

那麼當寫兩條線程Thread-A與Threab-B同時操作主存中的一個volatile變數i時,Thread-A寫了變數i,那麼:

  • Thread-A發出LOCK#指令
  • 發出的LOCK#指令鎖匯流排(或鎖緩存行),同時讓Thread-B高速緩存中的緩存行內容失效
  • Thread-A向主存回寫最新修改的i

Thread-B讀取變數i,那麼:

  • Thread-B發現對應地址的緩存行被鎖了,等待鎖的釋放,緩存一致性協議會保證它讀取到最新的值

由此可以看出,volatile關鍵字的讀和普通變數的讀取相比基本沒差別,差別主要還是在變數的寫操作上。

 

後記

之前對於volatile關鍵字的作用我個人還有一些會混淆的誤區,在深入理解volatile關鍵字的作用之後,感覺對volatile的理解深了許多。相信看到文章這裡的你,只要肯想、肯研究,一定會和我一樣有恍然大悟、茅塞頓開的感覺^_^

 

參考資料

《IA-32架構軟體開發人員手冊 第3捲:系統編程指南》

《Java併發編程的藝術》

《深入理解Java虛擬機:JVM高級特性與最佳實踐》

PrintAssembly查看volatile彙編代碼小記

緩存一致性(Cache Coherency)入門

聊聊高併發(三十四)Java記憶體模型那些事(二)理解CPU高速緩存的工作原理


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

-Advertisement-
Play Games
更多相關文章
  • C++與matlab混合編程——C++調用MATLAB函數 筆者最近在從事一個MFC相關的項目,要求將用Matlab實現的演算法通過應用MFC製作成一個小應用。其中有一部分內容需要求一個多元函數的最值。通過網路,我找到了兩個C++優化庫,dlib與MIDACO_Project ,可是這兩個庫中的優化函 ...
  • 安裝VS2013時,如何避開IE10的限制 VS就會告訴我們目前環境不適合安裝VS2013,必須升級IE版本到IE10. 如果不想安裝IE10,有沒有辦法呢? 答案肯定是有的。 將下麵一段文字,儲存為1.bat文檔,然後以管理員身份執行. 執行後,視窗回自動退出,然後你重新安裝VS即可。 ...
  • expect腳本內容 [root@ward-test script]# cat scp.exp #!/usr/bin/expect#Writen by : Ward/[email protected]#ScriptName: scp.expset timeout 10 set host [lindex ...
  • Windows下用Composer引入官方GitHub擴展包 1. 當你打開威武RC4版本的鏈接的時候,往下拉你可以看到這個,然後你要做的就是想到,百度Composer,看看是個什麼鬼,別想太多,跟著我走。接下來點擊Composer中文文檔,再點擊下載你會看到下載完後,點開如圖所示點擊next後發現 ...
  • Akka是一種消息驅動運算模式,它實現跨JVM程式運算的方式是通過能跨JVM的消息系統來調動分佈在不同JVM上ActorSystem中的Actor進行運算,前題是Akka的地址系統可以支持跨JVM定位。Akka的消息系統最高境界可以實現所謂的Actor位置透明化,這樣在Akka編程中就無須關註Act ...
  • synchronized 前言 相信大家都聽說過線程安全問題,在學習操作系統的時候有一個知識點是臨界資源,簡單的說就是一次只能讓一個進程操作的資源,但是我們在使用多線程的時候是併發操作的,並不能控制同時只對一個資源的訪問和修改,想要控制那麼有幾種操作,今天我們就來講講第一種方法:線程同步塊或者線程同 ...
  • Anaconda 是一個旗艦版的python安裝包, 因為普通的python沒有庫, 如果需要安裝一些重要的庫, 要經常一個一個下載,會非常麻煩. 所以這個一個集成的, 可以手動批量升級的軟體. 而且庫的安裝也很全下載速度快. 從官網下載完以後, next 安裝好. 配置環境變數, 把安裝的文件夾的... ...
  • CXF是webService的框架,能夠和spring無縫整合 ##服務端編寫 1.創建動態web項目 2.導入cxf和spring相關jar包(CXF核心包:cxf-2.4.2.jar) 3.在web.xml中配置CXF框架的核心Servlet 4.提供spring框架的配置文件applicati ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...