(轉)【Java線程】Java記憶體模型總結

來源:互聯網
上載者:User

標籤:.com   資料傳遞   style   條件   建立   不同   模型   種類   改變   

Java的並發採用的是共用記憶體模型(而非訊息傳遞模型),線程之間共用程式的公用狀態,線程之間通過寫-讀記憶體中的公用狀態來隱式進行通訊。多個線程之間是不能直接傳遞資料互動的,它們之間的互動只能通過共用變數來實現

同步顯式進行的。程式員必須顯式指定某個方法或某段代碼需要線上程之間互斥執行。

 

1、多線程通訊

 

1.1 記憶體模型

Java線程之間的通訊由Java記憶體模型(JMM)控制,JMM決定一個線程對共用變數的寫入何時對另一個線程可見。

從抽象的角度來看,JMM定義了線程和主記憶體之間的抽象關係:線程之間的共用變數儲存在主記憶體(main memory)中,每個線程都有一個私人的本地記憶體(local memory),本地記憶體中儲存了該線程以讀/寫共用變數的副本。本地記憶體是JMM的一個抽象概念,並不真實存在,它涵蓋了緩衝,寫緩衝區,寄存器以及其他的硬體和編譯器最佳化。Java記憶體模型的抽象如下:

 

線程間通訊的步驟:

  1. 首先,線程A把本地記憶體A中更新過的共用變數重新整理到主記憶體中去。
  2. 然後,線程B到主記憶體中去讀取線程A之前已更新過的共用變數。

 

  • 本地記憶體A和B有主記憶體中共用變數x的副本。
  • 假設初始時,這三個記憶體中的x值都為0。線程A在執行時,把更新後的x值(假設值為1)臨時存放在自己的本地記憶體A中。
  • 當線程A和線程B需要通訊時(如何激發?--隱式),線程A首先會把自己本地記憶體中修改後的x值重新整理到主記憶體中,此時主記憶體中的x值變為了1。
  • 隨後,線程B到主記憶體中去讀取線程A更新後的x值,此時線程B的本地記憶體的x值也變為了1。

從整體來看,這兩個步驟實質上是線程A在向線程B發送訊息,而且這個通訊過程必須要經過主記憶體。JMM通過控制主記憶體與每個線程的本地記憶體之間的互動,來為java程式員提供記憶體可見度保證。

 

1.2 可見度、有序性

例如在多個線程之間共用了Count類的一個對象,這個對象是被建立在主記憶體(堆記憶體)中,每個線程都有自己的本地記憶體(線程棧),工作記憶體儲存了主記憶體Count對象的一個副本,當線程操作Count對象時,首先從主記憶體複製Count對象到工作記憶體中,然後執行代碼count.count(),改變了num值,最後用工作記憶體Count重新整理主記憶體Count。

當一個對象在多個記憶體中都存在副本時,如果一個記憶體修改了共用變數,其它線程也應該能夠看到被修改後的值,此為可見度

 

一個運算賦值操作並不是一個原子性操作,多個線程執行時,CPU對線程的調度是隨機的,我們不知道當前程式被執行到哪步就切換到了下一個線程,一個最經典的例子就是銀行匯款問題,一個銀行賬戶存款100,這時一個人從該賬戶取10元,同時另一個人向該賬戶匯10元,那麼餘額應該還是100。那麼此時可能發生這種情況,A線程負責取款,B線程負責匯款,A從主記憶體讀到100,B從主記憶體讀到100,A執行減10操作,並將資料重新整理到主記憶體,這時主記憶體資料100-10=90,而B記憶體執行加10操作,並將資料重新整理到主記憶體,最後主記憶體資料100+10=110,顯然這是一個嚴重的問題,我們要保證A線程和B線程有序執行,先取款後匯款或者先匯款後取款,此為有序性

 

1.3 synchronized與volatile

一個線程執行互斥代碼過程如下:

  1. 獲得同步鎖;
  2. 清空工作記憶體;
  3. 從主記憶體拷貝對象副本到工作記憶體;
  4.  執行代碼(計算或者輸出等);
  5. 重新整理主記憶體資料;
  6. 釋放同步鎖。

所以,synchronized既保證了多線程的並發有序性,又保證了多線程的記憶體可見度。

 

volatile是第二種Java多線程同步的手段,根據JLS的說法,一個變數可以被volatile修飾,在這種情況下記憶體模型確保所有線程可以看到一致的變數值

class Test {        static volatile int i = 0, j = 0;        static void one() {            i++;            j++;        }        static void two() {            System.out.println("i=" + i + " j=" + j);        }    }    

加上volatile可以將共用變數i和j的改變直接響應到主記憶體中,這樣保證了i和j的值可以保持一致,然而我們不能保證執行two方法的線程是在i和j執行到什麼程度擷取到的,所以volatile可以保證記憶體可見度,不能保證並發有序性

 

如果沒有volatile,則代碼執行過程如下:

 

  1. 將變數i從主記憶體拷貝到工作記憶體;

  2. 重新整理主記憶體資料;

  3. 改變i的值;
  4. 將變數j從主記憶體拷貝到工作記憶體;

  5. 重新整理主記憶體資料;

  6. 改變j的值;

        

 

 

2、重排序

JMM屬於語言級的記憶體模型,它確保在不同的編譯器和不同的處理器平台之上,通過禁止特定類型的編譯器重排序和處理器重排序,為程式員提供一致的記憶體可見度保證。

對於編譯器沖排序,JMM的編譯器重定序會禁止特定類型的編譯器重排序(不是所有的編譯器重排序都要禁止)。

對於處理器重排序,JMM的處理器重定序會要求java編譯器在產生指令序列時,插入特定類型的記憶體屏障(memory barriers,intel稱之為memory fence)指令,通過記憶體屏障指令來禁止特定類型的處理器重排序(不是所有的處理器重排序都要禁止)。

 

引申:

 

在執行程式時為了提高效能,編譯器和處理器常常會對指令做重排序。重排序分三種類型:

  1. 編譯器最佳化的重排序。編譯器在不改變單線程程式語義的前提下,可以重新安排語句的執行順序。
  2. 指令級並行的重排序。現代處理器採用了指令級並行技術(Instruction-Level Parallelism, ILP)來將多條指令重疊執行。如果不存在資料依賴性,處理器可以改變語句對應機器指令的執行順序。
  3. 記憶體系統的重排序。由於處理器使用緩衝和讀/寫緩衝區,這使得載入和儲存操作看上去可能是在亂序執行。


上述的1屬於編譯器重排序,2和3屬於處理器重排序。這些重排序都可能會導致多線程程式出現記憶體可見度問題

 

2.1 資料依賴性

如果兩個操作訪問同一個變數,且這兩個操作中有一個為寫操作,此時這兩個操作之間就存在資料依賴性。資料依賴分下列三種類型:

 

名稱 程式碼範例 說明
寫後讀 a = 1;b = a; 寫一個變數之後,再讀這個位置。
寫後寫 a = 1;a = 2; 寫一個變數之後,再寫這個變數。
讀後寫 a = b;b = 1; 讀一個變數之後,再寫這個變數。

 

 

 

 

上面三種情況,只要重排序兩個操作的執行順序,程式的執行結果將會被改變。

 

前面提到過,編譯器和處理器可能會對操作做重排序。編譯器和處理器在重排序時,會遵守資料依賴性,編譯器和處理器不會改變存在資料依賴關係的兩個操作的執行順序。

注意,這裡所說的資料依賴性僅針對單個處理器中執行的指令序列和單個線程中執行的操作,不同處理器之間和不同線程之間的資料依賴性不被編譯器和處理器考慮

 

2.2 as-if-serial語義

 

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

【例】

double pi  = 3.14;    //A  double r   = 1.0;     //B  double area = pi * r * r; //C  
上面三個操作的資料依賴關係如所示:

 

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

as-if-serial語義把單線程程式保護了起來,遵守as-if-serial語義的編譯器,runtime 和處理器共同為編寫單線程程式的程式員建立了一個幻覺:單線程程式是按程式的順序來執行的。as-if-serial語義使單線程程式員無需擔心重排序會干擾他們,也無需擔心記憶體可見度問題

 

2.3 happens-before

 

從JDK5開始,java使用新的JSR -133記憶體模型。JSR-133提出了happens-before的概念,通過這個概念來闡述操作之間的記憶體可見度。如果一個操作執行的結果需要對另一個操作可見,那麼這兩個操作之間必須存在happens-before關係。這裡提到的兩個操作既可以是在一個線程之內,也可以是在不同線程之間。 與程式員密切相關的happens-before規則如下:

  • 程式順序規則:一個線程中的每個操作,happens- before 於該線程中的任意後續操作。
  • 監視器鎖規則:對一個監視器鎖的解鎖,happens- before 於隨後對這個監視器鎖的加鎖。
  • volatile變數規則:對一個volatile域的寫,happens- before 於任意後續對這個volatile域的讀。
  • 傳遞性:如果A happens- before B,且B happens- before C,那麼A happens- before C。

注意,兩個操作之間具有happens-before關係,並不意味著前一個操作必須要在後一個操作之前執行!happens-before僅僅要求前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前(the first is visible to and ordered before the second)。happens- before的定義很微妙,後文會具體說明happens-before為什麼要這麼定義。

 

【例】根據happens- before的程式順序規則,上面計算圓的面積的範例程式碼存在三個happens- before關係:

 

  1. A happens- before B;
  2. B happens- before C;
  3. A happens- before C;

 

這裡的第3個happens- before關係,是根據happens- before的傳遞性推匯出來的。

這裡A happens- before B,但實際執行時B卻可以排在A之前執行(看上面的重排序後的執行順序)。A happens- before B,JMM並不要求A一定要在B之前執行。JMM僅僅要求前一個操作(執行的結果)對後一個操作可見,且前一個操作按順序排在第二個操作之前。這裡操作A的執行結果不需要對操作B可見;而且重排序操作A和操作B後的執行結果,與操作A和操作B按happens- before順序執行的結果一致。在這種情況下,JMM會認為這種重排序並不非法(not illegal),JMM允許這種重排序。

在電腦中,軟體技術和硬體技術有一個共同的目標:在不改變程式執行結果的前提下,儘可能的開發並行度。編譯器和處理器遵從這一目標,從happens- before的定義我們可以看出,JMM同樣遵從這一目標。

2.4 重排序對多線程的影響

 

現在讓我們來看看,重排序是否會改變多線程程式的執行結果。【例】:

class ReorderExample {      int a = 0;      boolean flag = false;        public void writer() {          a = 1;                   //1          flag = true;             //2      }        Public void reader() {          if (flag) {                //3              int i =  a * a;        //4              ……          }      }  }  

flag變數是個標記,用來標識變數a是否已被寫入。這裡假設有兩個線程A和B,A首先執行writer()方法,隨後B線程接著執行reader()方法。線程B在執行操作4時,能否看到線程A在操作1對共用變數a的寫入?

答案是:不一定能看到。

 

由於操作1和操作2沒有資料依賴關係,編譯器和處理器可以對這兩個操作重排序;同樣,操作3和操作4沒有資料依賴關係(?),編譯器和處理器也可以對這兩個操作重排序。讓我們先來看看,當操作1和操作2重排序時,可能會產生什麼效果?請看下面的程式執行時序圖:

如所示,操作1和操作2做了重排序。程式執行時,線程A首先寫標記變數flag,隨後線程B讀這個變數。由於條件判斷為真,線程B將讀取變數a。此時,變數a還根本沒有被線程A寫入,在這裡多線程程式的語義被重排序破壞了!

 

下面再讓我們看看,當操作3和操作4重排序時會產生什麼效果(藉助這個重排序,可以順便說明控制依賴性)。下面是操作3和操作4重排序後,程式的執行時序圖:

在程式中,操作3和操作4存在控制依賴關係。當代碼中存在控制依賴性時,會影響指令序列執行的並行度。為此,編譯器和處理器會採用猜測(Speculation)執行來克服控制相關性對並行度的影響。以處理器的猜測執行為例,執行線程B的處理器可以提前讀取並計算a*a,然後把計算結果臨時儲存到一個名為重排序緩衝(reorder buffer ROB)的硬體緩衝中。當接下來操作3的條件判斷為真時,就把該計算結果寫入變數i中。

我們可以看出,猜測執行實質上對操作3和4做了重排序。重排序在這裡破壞了多線程程式的語義!

在單線程程式中,對存在控制依賴的操作重排序,不會改變執行結果(這也是as-if-serial語義允許對存在控制依賴的操作做重排序的原因);但在多線程程式中,對存在控制依賴的操作重排序,可能會改變程式的執行結果

 

3、順序一致性

3.1 資料競爭

 

當程式未正確同步時,就會存在資料競爭。java記憶體模型規範對資料競爭的定義如下:

  • 在一個線程中寫一個變數,
  • 在另一個線程讀同一個變數,
  • 而且寫和讀沒有通過同步來排序。

 

當代碼中包含資料競爭時,程式的執行往往產生違反直覺的結果(前一章的樣本正是如此)。如果一個多線程程式能正確同步,這個程式將是一個沒有資料競爭的程式。

JMM對正確同步的多線程程式的記憶體一致性做了如下保證:

  • 如果程式是正確同步的,程式的執行將具有順序一致性(sequentially consistent)——即程式的執行結果與該程式在順序一致性記憶體模型中的執行結果相同。這裡的同步是指廣義上的同步,包括對常用同步原語(lock,volatile和final)的正確使用。
3.2 順序一致性記憶體模型

順序一致性記憶體模型有兩大特性:

  • 一個線程中的所有操作必須按照程式的順序來執行。
  • (不管程式是否同步)所有線程都只能看到一個單一的操作執行順序。在順序一致性記憶體模型中,每個操作都必須原子執行且立刻對所有線程可見。

 

順序一致性記憶體模型為程式員提供的視圖如下。在概念上,順序一致性模型有一個單一的全域記憶體,這個記憶體通過一個左右擺動的開關可以串連到任意一個線程。同時,每一個線程必須按程式的順序來執行記憶體讀/寫操作。在任意時間點最多隻能有一個線程可以串連到記憶體。當多個線程並發執行時,圖中的開關裝置能把所有線程的所有記憶體讀/寫操作序列化。

為了更好的理解,下面我們通過兩個來對順序一致性模型的特性做進一步的說明。

假設有兩個線程A和B並發執行。其中A線程有三個操作,它們在程式中的順序是:A1->A2->A3。B線程也有三個操作,它們在程式中的順序是:B1->B2->B3。

假設這兩個線程使用監視器來正確同步:A線程的三個操作執行後釋放監視器,隨後B線程擷取同一個監視器。那麼程式在順序一致性模型中的執行效果將如所示:

假設這兩個線程沒有做同步,下面是這個未同步程式在順序一致性模型中的執行:

 

未同步程式在順序一致性模型中雖然整體執行順序是無序的,但所有線程都只能看到一個一致的整體執行順序。以為例,線程A和B看到的執行順序都是:B1->A1->A2->B2->A3->B3。之所以能得到這個保證是因為順序一致性記憶體模型中的每個操作必須立即對任意線程可見。

但是,在JMM中就沒有這個保證。未同步程式在JMM中不但整體的執行順序是無序的,而且所有線程看到的操作執行順序也可能不一致。比如,在當前線程把寫過的資料緩衝在本地記憶體中,且還沒有重新整理到主記憶體之前,這個寫操作僅對當前線程可見;從其他線程的角度來觀察,會認為這個寫操作根本還沒有被當前線程執行。只有當前線程把本地記憶體中寫過的資料重新整理到主記憶體之後,這個寫操作才能對其他線程可見。在這種情況下,當前線程和其它線程看到的操作執行順序將不一致。

 

3.3 同步程式的執行特性

【例】

class SynchronizedExample {    int a = 0;    boolean flag = false;      public synchronized void writer() {      a = 1;      flag = true;    }      public synchronized void reader() {      if (flag) {          int i = a;          ……      }    }  }  



在順序一致性模型中,所有操作完全按程式的順序串列執行。而在JMM中,臨界區內的代碼可以重排序。

 

3.4 未同步程式的執行特性

 

對於未同步或未正確同步的多線程程式,JMM只提供最小安全性:線程執行時讀取到的值,要麼是之前某個線程寫入的值,要麼是預設值(0,null,false),JMM保證線程讀操作讀取到的值不會無中生有(out of thin air)的冒出來。

為了實現最小安全性,JVM在堆上指派至時,首先會清零記憶體空間,然後才會在上面指派至(JVM內部會同步這兩個操作)。因此,在以清零的記憶體空間(pre-zeroed memory)指派至時,域的預設初始化已經完成了。

 

JMM不保證未同步程式的執行結果與該程式在順序一致性模型中的執行結果一致。因為未同步程式在順序一致性模型中執行時,整體上是無序的,其執行結果無法預知。保證未同步程式在兩個模型中的執行結果一致毫無意義。

和順序一致性模型一樣,未同步程式在JMM中的執行時,整體上也是無序的,其執行結果也無法預知。同時,未同步程式在這兩個模型中的執行特性有下面幾個差異

  1. 順序一致性模型保證單線程內的操作會按程式的順序執行,而JMM不保證單線程內的操作會按程式的順序執行(比如上面正確同步的多線程程式在臨界區內的重排序)。——前文已述
  2. 順序一致性模型保證所有線程只能看到一致的操作執行順序,而JMM不保證所有線程能看到一致的操作執行順序。——前文已述
  3. JMM不保證對64位的long型和double型變數的讀/寫操作具有原子性,而順序一致性模型保證對所有的記憶體讀/寫操作都具有原子性。

關於第三點:

第三點差異與處理器匯流排的工作機制密切相關。在電腦中,資料通過匯流排在處理器和記憶體之間傳遞。每次處理器和記憶體之間的資料傳遞都是通過一系列步驟來完成的,這一系列步驟稱之為匯流排事務(bus transaction)。匯流排事務包括讀事務(read transaction)和寫事務(write transaction)。讀事務從記憶體傳送資料到處理器,寫事務從處理器傳送資料到記憶體,每個事務會讀/寫記憶體中一個或多個物理上連續的字。這裡的關鍵是,匯流排會同步試圖並發使用匯流排的事務。在一個處理器執行匯流排事務期間,匯流排會禁止其它所有的處理器和I/O裝置執行記憶體的讀/寫。

 

在一些32位的處理器上,如果要求對64位元據的讀/寫操作具有原子性,會有比較大的開銷。為了照顧這種處理器,java語言規範鼓勵但不強求JVM對64位的long型變數和double型變數的讀/寫具有原子性。當JVM在這種處理器上運行時,會把一個64位long/ double型變數的讀/寫操作拆分為兩個32位的讀/寫操作來執行。這兩個32位的讀/寫操作可能會被分配到不同的匯流排事務中執行,此時對這個64位變數的讀/寫將不具有原子性。

當單個記憶體操作不具有原子性,將可能會產生意想不到後果。請看下面:

如所示,假設處理器A寫一個long型變數,同時處理器B要讀這個long型變數。處理器A中64位的寫操作被拆分為兩個32位的寫操作,且這兩個32位的寫操作被分配到不同的寫事務中執行。同時處理器B中64位的讀操作被拆分為兩個32位的讀操作,且這兩個32位的讀操作被分配到同一個的讀事務中執行。當處理器A和B按的時序來執行時,處理器B將看到僅僅被處理器A“寫了一半“的無效值。


4、volatile

把對volatile變數的單個讀/寫,看成是使用同一個監視器鎖對這些單個讀/寫操作做了同步。對一個volatile變數的讀,總是能看到(任意線程)對這個volatile變數最後的寫入。

這意味著即使是64位的long型和double型變數,只要它是volatile變數,對該變數的讀寫就將具有原子性。如果是多個volatile操作或類似於volatile++這種複合操作,這些操作整體上不具有原子性。

簡而言之,volatile變數自身具有下列特性:

  • 可見度。對一個volatile變數的讀,總是能看到(任意線程)對這個volatile變數最後的寫入。
  • 原子性:對任意單個volatile變數的讀/寫具有原子性,但類似於volatile++這種複合操作不具有原子性。
4.1 volatile寫-讀建立的happens before關係

從JSR-133開始,volatile變數的寫-讀可以實現線程之間的通訊。

從記憶體語義的角度來說,volatile與監視器鎖有相同的效果:volatile寫和監視器的釋放有相同的記憶體語義;volatile讀與監視器的擷取有相同的記憶體語義

class VolatileExample {      int a = 0;      volatile boolean flag = false;        public void writer() {          a = 1;                   //1          flag = true;               //2      }        public void reader() {          if (flag) {                //3              int i =  a;           //4              ……          }      }  }  

假設線程A執行writer()方法之後,線程B執行reader()方法。根據happens before規則,這個過程建立的happens before 關係可以分為兩類:

  1. 根據程式次序規則,1 happens before 2; 3 happens before 4。
  2. 根據volatile規則,2 happens before 3。
  3. 根據happens before 的傳遞性規則,1 happens before 4。

 

 

 中,每一個箭頭連結的兩個節點,代表了一個happens before 關係。黑色箭頭表示程式順序規則;橙色箭頭表示volatile規則;藍色箭頭表示組合這些規則後提供的happens before保證。

這裡A線程寫一個volatile變數後,B線程讀同一個volatile變數。A線程在寫volatile變數之前所有可見的共用變數,在B線程讀同一個volatile變數後,將立即變得對B線程可見。

 

4.2 volatile寫-讀的記憶體語義

 volatile寫的記憶體語義如下:

  • 當寫一個volatile變數時,JMM會把該線程對應的本地記憶體中的共用變數重新整理到主記憶體。

以上面樣本程式VolatileExample為例,假設線程A首先執行writer()方法,隨後線程B執行reader()方法,初始時兩個線程的本地記憶體中的flag和a都是初始狀態。

是線程A執行volatile寫後,共用變數的狀態。線程A在寫flag變數後,本地記憶體A中被線程A更新過的兩個共用變數的值被重新整理到主記憶體中。此時,本地記憶體A和主記憶體中的共用變數的值是一致的。

 

volatile讀的記憶體語義如下:

  • 當讀一個volatile變數時,JMM會把該線程對應的本地記憶體置為無效。線程接下來將從主記憶體中讀取共用變數。

下面是線程B讀同一個volatile變數後,共用變數的狀態。在讀flag變數後,本地記憶體B已經被置為無效。此時,線程B必須從主記憶體中讀取共用變數。線程B的讀取操作將導致本地記憶體B與主記憶體中的共用變數的值也變成一致的了。

把volatile寫和volatile讀這兩個步驟綜合起來看的話,在讀線程B讀一個volatile變數後,寫線程A在寫這個volatile變數之前所有可見的共用變數的值都將立即變得對讀線程B可見。

下面對volatile寫和volatile讀的記憶體語義做個總結:

  • 線程A寫一個volatile變數,實質上是線程A向接下來將要讀這個volatile變數的某個線程發出了(其對共用變數所在修改的)訊息。
  • 線程B讀一個volatile變數,實質上是線程B接收了之前某個線程發出的(在寫這個volatile變數之前對共用變數所做修改的)訊息。
  • 線程A寫一個volatile變數,隨後線程B讀這個volatile變數,這個過程實質上是線程A通過主記憶體向線程B發送訊息。
4.3 volatile記憶體語義的實現

為了實現volatile記憶體語義,JMM會分別限制編譯器重排序和處理器重排序。下面是JMM針對編譯器制定的volatile重定序表:

 

是否能重排序 第二個操作
第一個操作 普通讀/寫 volatile讀 volatile寫
普通讀/寫     NO
volatile讀 NO NO NO
volatile寫   NO NO

舉例來說,第三行最後一個儲存格的意思是:在程式順序中,當第一個操作為普通變數的讀或寫時,如果第二個操作為volatile寫,則編譯器不能重排序這兩個操作。

從上表我們可以看出:

  • 當第二個操作是volatile寫時,不管第一個操作是什麼,都不能重排序。這個規則確保volatile寫之前的操作不會被編譯器重排序到volatile寫之後。
  • 當第一個操作是volatile讀時,不管第二個操作是什麼,都不能重排序。這個規則確保volatile讀之後的操作不會被編譯器重排序到volatile讀之前。
  • 當第一個操作是volatile寫,第二個操作是volatile讀時,不能重排序。

 

(轉)【Java線程】Java記憶體模型總結

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.