主頁 > 後端開發 > 并發編程之JMM&Volatile底層原理剖析

并發編程之JMM&Volatile底層原理剖析

2020-10-16 21:25:53 後端開發

初步認識 Volatile

一段代碼引發的思考,下面這段 代碼演示了使用valatile和沒有使用volatile關鍵字對于變數更新的影響

public class App {
    public volatile static boolean stop = false;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            int i = 0;
            while(!stop){
                i ++;
            }
        });
        t1.start();
        System.out.println("begin");
        Thread.sleep(1000);
        stop = true;
    }
}

volatile的作用

可以使得在多處理器環境下保證共享變數的可見性,什么是可見性?

在單執行緒的環境下,如果向一個變數先寫入一個值,然后再沒有寫干涉的情況下讀取這個變數,這個時候讀取到的這個變數值應該是之前寫入的值,這本來是一個很正常的事情,但是在多執行緒環境下,讀和寫發生在不同執行緒中的時候可能會出現:讀執行緒不能即使讀取到其他執行緒寫入的最新值,這就是所謂的可見性,為了實作多執行緒寫入的記憶體可見性,必須使用一些機制,而volatile就是這樣一種機制

volatile 關鍵字是如何保證可見性的?

在運行main函式之前,加入虛擬機引數

-server -Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,*XXX.function(xxx替換成實際運行的類,function替換方法名)

然后在輸出的結果中,查找下 lock 指令, 會發現,在修改帶有 volatile 修飾的成員變數時,會多一個lock 指令, lock是一種控制指令, 在多處理器環境下, lock 匯編指令可以基于總線鎖或者快取鎖的機制來達到可見性的一個效果

為了更好的理解可見性的本質, 我們需要從硬體層面進行梳理

從硬體層面了解可見性的本質

一臺計算機最核心的組件時CPU,記憶體以及I/O設備不斷迭代升級來提升計算機處理能力之外,還有一個非常核心的矛盾點,就是這三者在處理速度的差異,CPU 的計算速度是非常快的,記憶體次之、最后是 IO 設備比如磁盤,而在絕大部分的程式中,一定會存在記憶體訪問,有些可能還會存在 I/O 設備的訪問,

為了提升計算性能, CPU 從單核升級到了多核甚至用到了超執行緒技術最大化提高 CPU 的處理性能,但是僅僅提升CPU 性能還不夠,如果后面兩者的處理性能沒有跟上,意味著整體的計算效率取決于最慢的設備, 為了平衡三者的速度差異,最大化的利用 CPU 提升性能,從硬體、作業系統、編譯器等方面都做出了很多的優化,

  1. CPU 增加了高速快取
  2. 作業系統增加了行程、執行緒,通過 CPU 的時間片切換最大化的提升 CPU 的使用率
  3. 編譯器的指令優化,更合理的去利用好 CPU 的高速快取

然而每一種優化,都會帶來相應的問題,而這些問題也是導致執行緒安全性問題的根源, 為了了解前面提到的可見性問題的本質,我們有必要去了解這些優化的程序

CPU快取架構

CPU快取即高速緩沖存盤器,是位于CPU與主記憶體間的一種容量較小但速度很高的存盤器,由于CPU的速度遠高于主記憶體,CPU直接從記憶體中存取資料要等待一定時間周期,Cache中保存著CPU剛用過或回圈使用的一部分資料,當CPU再次使用該部分資料時可從Cache中直接呼叫,減少CPU的等待時間,提高了系統的效率

通過高速快取的存盤互動很好的解決了處理器與記憶體的速度矛盾,但是也為計算機系統帶來了更高的復雜度,因為它引入了一個新的問題,快取一致性

什么叫快取一致性呢?

首先,有了高速快取的存在以后, 每個 CPU 的處理程序是,先將計算需要用到的資料快取在 CPU 高速快取中,在 CPU進行計算時,直接從高速快取中讀取資料并且在計算完成,之后寫入到快取中, 在整個運算程序完成后,再把快取中的資料同步到主記憶體

由于在多 CPU 種,每個執行緒可能會運行在不同的 CPU 內,并且每個執行緒擁有自己的高速快取, 同一份資料可能會被快取到多個 CPU 中,如果在不同 CPU 中運行的不同執行緒看到同一份記憶體的快取值不一樣就會存在快取不一致的問題,

為了解決快取不一致的問題,在 CPU 層面做了很多事情,

主要提供了兩種解決辦法l

1. 總線鎖

2. 快取鎖

JMM&volatile.jpg

總線鎖,簡單來說就是,在多 cpu 下,當其中一個處理器要對共享記憶體進行操作的時候,在總線上發出一個 LOCK#信號,這個信號使得其他處理器無法通過總線來訪問到共享記憶體中的資料, 總線鎖定把 CPU 和記憶體之間的通信鎖住了,這使得鎖定期間,其他處理器不能操作其他記憶體地址的資料,所以總線鎖定的開銷比較大, 這種機制顯然是不合適的

如何優化呢? 最好的方法就是控制鎖的保護粒度,我們只需要保證對于被多個 CPU 快取的同一份資料是一致的就行, 所以引入了快取鎖, 它核心機制是基于快取一致性協議來實作的

快取一致性協議

為了達到資料訪問的一致,需要各個處理器在訪問快取時遵循一些協議,在讀寫時根據協議來操作,常見的協議有MSI, MESI, MOSI 等, 最常見的就是 MESI 協議

MESI 表示快取行的四種狀態,分別是:

  1. M(Modify) 表示共享資料只快取在當前 CPU 快取中,并且是被修改狀態,也就是快取的資料和主記憶體中的資料不一致

2.E(Exclusive) 表示快取的獨占狀態,資料只快取在當前CPU 快取中,并且沒有被修改

3.S(Shared) 表示資料可能被多個 CPU 快取,并且各個快取中的資料和主記憶體資料一致

4.I(Invalid) 表示快取已經失效

在 MESI 協議中,每個快取的快取控制器不僅知道自己的讀寫操作,而且也監聽(snoop)其它 Cache 的讀寫操作

關鍵 對于 MESI 協議, 從 CPU 讀寫角度來說會遵循以下原則:CPU 讀請求:快取處于 M、 E、 S 狀態都可以被讀取, I 狀態 CPU 只能從主存中讀取資料CPU 寫請求:快取處于 M、 E 狀態才可以被寫,對于 S 狀態的寫,需要將其他 CPU 中快取行置為無效才可寫使用總線鎖和快取鎖機制之后, CPU 對于記憶體的操作大概可以抽象成下面這樣的結構,從而達到快取一致性效果

MESI 優化帶來的可見性問題

MESI 協議雖然可以實作快取的一致性,但是也會存在一些問題,

就是各個 CPU 快取行的狀態是通過訊息傳遞來進行的, 如果 CPU0 要對一個在快取中共享的變數進行寫入,首先需要發送一個失效的訊息給到其他快取該資料的 CPU(Invalid),并且要等到他們的確認回執, CPU0 在這段時間內都會處于阻塞狀態, 為了避免阻塞帶來的資源浪費, 在 cpu 中引入了 Store Bufferes

CPU0 只需要在寫入共享資料時,直接把資料寫入到 storebufferes 中, 同時發送 invalidate 訊息,然后繼續去處理其他指令,當收到其他所有 CPU 發送了 invalidate acknowledge 訊息時, 再將 store bufferes 中的資料資料存盤至 cache line中,最后再從快取行同步到主記憶體,

但是這種優化存在兩個問題

1. 資料什么時候提交是不確定的,因為需要等待其他 cpu給回復才會進行資料同步,這里其實是一個異步操作

2. 引入了 storebufferes 后,處理器會先嘗試從 storebuffer中讀取值,如果 storebuffer 中有資料,則直接從storebuffer 中讀取,否則就再從快取行中讀取

看個例子:

//cpu已經快取了Flag
//M(Modify)  E(Exclusive) S(Shared)  I(Invalid) 狀態
value = 3 //(S)
void cpu0(){
    value = 10; //( M) ->[ storebufferes ->通知其他cpu快取行失效(i)] 
    Flag = true;//(E)
}
void cpu1(){
   if(Flag){//true
       assert value ==10;//flase
   }
}

cpu0和cpu1分別在倆個獨立cpu上執行,假如cpu0快取行中快取了isFlag這個共享變數且狀態(E),而Vlaue可能是(S)狀態,

這時候,CPU0在執行的時候,會先把value=10寫入到storebuffer中,并且通知其他快取了value的cpu.,在等待其他CPU通知結果的時候,cpu0會先執行isFlag=true的指令,

而因為當前cpu0快取了isFlag并且是(E)狀態,所以可以直接修改isFlag=true,但是value值還不等于10.

這種情況我們可以認為是CPU的亂序執行,也可以認為是重排序,這種重排序會帶來可見性問題,

從硬體層面很難去知道軟體層面上的這種前后依賴關系,沒有辦法通過某種手段自動去解決,所以在 CPU 層面提供了 memory barrier(記憶體屏障)的指令,從硬體層面來看這個 memroy barrier 就是 CPU flushstore bufferes 中的指令,軟體層面可以決定在適當的地方來插入記憶體屏障,

總的來說,記憶體屏障的作用可以通過防止 CPU 對記憶體的亂序訪問來保證共享資料在多執行緒并行執行下的可見性但是這個屏障怎么來加呢?回到最開始我們講 volatile 關鍵字的代碼,這個關鍵字會生成一個 Lock 的匯編指令,這個指令其實就相當于實作了一種記憶體屏障.

這個時候問題又來了, 記憶體屏障、重排序這些東西好像是和平臺以及硬體架構有關系的, 作為 Java 語言的特性,一次撰寫多處運行, 我們不應該考慮平臺相關的問題,并且這些所謂的記憶體屏障也不應該讓程式員來關心,

JMM

什么是JMM

JMM 全稱是 Java Memory Model. 什么是 JMM 呢?

JMM模型跟CPU快取模型結構類似,是基于CPU快取模型建立起來的,JMM模型是標準化的,屏蔽掉了底層不同計算機的區別,對于硬體記憶體來說只有暫存器、快取記憶體、主記憶體的概念,并沒有作業記憶體(執行緒私有資料區域)和主記憶體(堆記憶體)之分,因為JMM只是一種抽象的概念,是一組規則,并不實際存在,不管是作業記憶體的資料還是主記憶體的資料,對于計算機硬體來說都會存盤在計算機主記憶體中,當然也有可能存盤到CPU快取或者暫存器中,

通過這些規則來規范對記憶體的讀寫操作從而保證指令的正確性,它解決了 CPU 多級快取、處理器優化、指令重排序導致的記憶體訪問問題,保證了并發場景下的可見性

需要注意的是, JMM 并沒有限制執行引擎使用處理器的暫存器或者高速快取來提升指令執行速度,也沒有限制編譯器對指令進行重排序,也就是說在 JMM 中,也會存在快取一致性問題和指令重排序問題,只是 JMM 把底層的問題抽象到 JVM 層面,再基于 CPU 層面提供的記憶體屏障指令,以及限制編譯器的重排序來解決并發問題

java 記憶體模型底層實作可以簡單的認為: 通過記憶體屏障(memory barrier)禁止重排序,即時編譯器根據具體的底層體系架構,將這些記憶體屏障替換成具體的 CPU 指令,對于編譯器而言,記憶體屏障將限制它所能做的重排序優化,而對于處理器而言,記憶體屏障將會導致快取的重繪操作,比如,對于 volatile,編譯器將在 volatile 欄位的讀寫操作前后各插入一些記憶體屏障

簡單來說, JMM 提供了一些禁用快取以及進制重排序的方法,來解決可見性和有序性問題, 這些方法大家都很熟悉:volatile、 synchronized、 final;以及HappenBefore規則

重排序

注意:X86處理器不會對讀-讀、讀-寫和寫-寫操作做重排序, 會省略掉這3種操作型別對應的記憶體屏障,僅會對寫-讀操作做重排序,所以volatile寫-讀操作只需要在volatile寫后插入StoreLoad屏障

為了提高程式的執行性能,編譯器和處理器都會對指令做重排序,其中處理器的重排序在前面已經分析過了, 所謂的重排序其實就是指執行的指令順序,編譯器的重排序指的是程式撰寫的指令在編譯之后,指令可能會產生重排序來優化程式的執行性能

從源代碼到最終執行的指令,可能會經過三種重排序

2 和 3 屬于處理器重排序,這些重排序可能會導致可見性問題,

編譯器的重排序, JMM 提供了禁止特定型別的編譯器重排序

處理器重排序, JMM 會要求編譯器生成指令時,會插入記憶體屏障來禁止處理器重排序

記憶體屏障

硬體層提供了一系列的記憶體屏障 memory barrier / memory fence(Intel的提法)來提供一致性的能力,拿X86平臺來說,有幾種主要的記憶體屏障:

  1. lfence,是一種Load Barrier 讀屏障
  2. sfence, 是一種Store Barrier 寫屏障
  3. mfence, 是一種全能型的屏障,具備lfence和sfence的能力
  4. Lock前綴,Lock不是一種記憶體屏障,但是它能完成類似記憶體屏障的功能,Lock會對CPU總線和高速快取加鎖,可以理解為CPU指令級的一種鎖,它后面可以跟ADD, ADC, AND, BTC, BTR, BTS, CMPXCHG, CMPXCH8B, DEC, INC, NEG, NOT, OR, SBB, SUB, XOR, XADD, and XCHG等指令,

記憶體屏障有兩個能力:

  1. 阻止屏障兩邊的指令重排序
  2. 重繪處理器快取/沖刷處理器快取

對Load Barrier來說,在讀指令前插入讀屏障,可以讓高速快取中的資料失效,重新從主記憶體加載資料

對Store Barrier來說,在寫指令之后插入寫屏障,能讓寫入快取的最新資料寫回到主記憶體

Lock前綴實作了類似的能力,它先對總線和快取加鎖,然后執行后面的指令,最后釋放鎖后會把高速快取中的資料重繪回主記憶體,在Lock鎖住總線的時候,其他CPU的讀寫請求都會被阻塞,直到鎖釋放,

JMM 層面的記憶體屏障

為了保證記憶體可見性, Java 編譯器在生成指令序列的適當位置會插入記憶體屏障來禁止特定型別的處理器的重排序,在 JMM 中把記憶體屏障分為四類

  1. load1 loadload load2 -> load1 早于load2 讀讀屏障
  2. strore1 strorestore store2 -> 寫寫屏障
  3. load loadstore store ->讀寫屏障
  4. store storeload load ->全屏障

我們通過 javap -v xxx.class命令查看匯編指令會發現,假如了volatile關鍵字后有這么一條指令

flags: ACC_PUBLIC, ACC_STATIC, ACC_VOLATILE

煩是volatile關鍵字 最后一定會執行 stroeload();

不同平臺下實作的檔案

如果存在重排序情況下, JMM提供了倆級別記憶體屏障(cpu,語言級別)

lock匯編指令:cpu級別記憶體屏障,鎖住快取行

volatile :語言級別記憶體屏障,禁止編譯器對代碼優化(重排序)

as-if-serial

as-if-serial語意的意思是:不管怎么重排序(編譯器和處理器為了提高并行度),(單執行緒)程式的執行結果不能被改變,編譯器、runtime和處理器都必須遵守as-if-serial語意,

為了遵守as-if-serial語意,編譯器和處理器不會對存在資料依賴關系的操作做重排序,因為這種重排序會改變執行結果,但是,如果操作之間不存在資料依賴關系,這些操作就可能被編譯器和處理器重排序,

a =1;
b=2;
c = a*b;

A和C之間存在資料依賴關系,同時B和C之間也存在資料依賴關系,因此在最終執行的指令序列中,C不能被重排序到A和B的前面(C排到A和B的前面,程式的結果將會被改變),但A和B之間沒有資料依賴關系,編譯器和處理器可以重排序A和B之間的執行順序

happens-before

在JMM中,如果一個操作執行的結果需要對另一個操作可見,那么這兩個操作之間必須存在happens-before關系,

  1. 程式次序規則:一個執行緒內,按照代碼順序,書寫在前面的操作先行發生于書寫在后面的操作;
  2. 鎖定規則:一個unLock操作先行發生于后面對同一個鎖的lock操作;
  3. volatile變數規則:對一個變數的寫操作先行發生于后面對這個變數的讀操作;
  4. 傳遞規則:如果操作A先行發生于操作B,而操作B又先行發生于操作C,則可以得出操作A先行發生于操作C;
  5. 執行緒啟動規則:Thread物件的start()方法先行發生于此執行緒的每個一個動作;
  6. 執行緒中斷規則:對執行緒interrupt()方法的呼叫先行發生于被中斷執行緒的代碼檢測到中斷事件的發生;
  7. 執行緒終結規則:執行緒中所有的操作都先行發生于執行緒的終止檢測,我們可以通過Thread.join()方法結束、Thread.isAlive()的回傳值手段檢測到執行緒已經終止執行;
  8. 物件終結規則:一個物件的初始化完成先行發生于他的finalize()方法的開始;

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

標籤:python

上一篇:Java之父都需要的一本能夠更深入地了解Java編程語言的書

下一篇:虎牙、斗魚正式達成合并協議;?中國廣電正式成立,或催生5G發展新格局;Linux 5.9 釋出|極客頭條

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