java基礎之:多線程實現/啟動/狀態+同步+線程池

來源:互聯網
上載者:User

 

 

轉自:http://zangweiren.blog.51cto.com/412366/94386

原作者:臧圩人·海納百川

 

 

線程或者說多線程,是我們處理多任務的強大工具。

線程和進程是不同的,每個進程都是一個獨立啟動並執行程式,擁有自己的變數,且不同進程間的變數不能共用;

而線程是運行在進程內部的,每個正在啟動並執行進程至少有一個線程,而且不同的線程之間可以在進程範圍內共用資料。

也就是說進程有自己獨立的儲存空間,而線程是和它所屬的進程內的其他線程共用一個儲存空間。

 

線程的使用可以使我們能夠並行地處理一些事情。線程通過並行的處理給使用者帶來更好的使用體驗。

比如使用郵件系統(運行outlook、Thunderbird、foxmail等進程)時,你當然不希望它們在收取新郵件的時候,導致你連已經收下來的郵件都無法閱讀,而只能等待收取郵件操作執行完畢。

這正是線程的意義所在。 

 

【實現線程的方式】

 

實現線程的方式有兩種: 

繼承java.lang.Thread,並重寫它的run()方法,將線程的執行主體放入其中。

實現java.lang.Runnable介面,實現它的run()方法,並將線程的執行主體放入其中。

//這是繼承Thread類實現線程的樣本:<br />public class ThreadTest extends Thread {<br />public void run() {<br />// 在這裡編寫線程執行的主體<br />// do something<br />}<br />}<br />//這是實現Runnable介面實現多線程的樣本:<br />public class RunnableTest implements Runnable {<br />public void run() {<br />// 在這裡編寫線程執行的主體<br />// do something<br />}<br />} 

 

這兩種實現方式的區別並不大。繼承Thread類的方式實現起來較為簡單,但是繼承它的類就不能再繼承別的類了,因此也就不能繼承別的類的有用的方法了。而使用是想Runnable介面的方式就不存在這個問題了,而且這種實現方式將線程主體和線程對象本身分離開來,邏輯上也較為清晰,所以推薦大家更多地採用這種方式。 

 

 

【如何啟動線程 】

 

我們通過以上兩種方式實現了一個線程之後,線程的執行個體並沒有被建立,因此它們也並沒有被運行。

我們要啟動一個線程,必須調用方法來啟動它,這個方法就是Thread類的start()方法,而不是run()方法。

 

(既不是我們繼承Thread類重寫的run()方法,也不是實現Runnable介面的run()方法)。

run()方法中包含的是線程的主體,也就是這個線程被啟動後將要啟動並執行代碼,它跟線程的啟動沒有任何關係。

 

上面兩種實現線程的方式在啟動時會有所不同。 

 

繼承Thread類的啟動方式: 

public class ThreadStartTest {

public static void main(String[] args) {

// 建立一個線程執行個體

ThreadTest tt = new ThreadTest();

// 啟動線程

tt.start();

}

}

 

實現Runnable介面的啟動方式: 

public class RunnableStartTest {

public static void main(String[] args) {

// 建立一個線程執行個體

Thread t = new Thread(new RunnableTest());

// 啟動線程

t.start();

}

}

 

實際上這兩種啟動線程的方式原理是一樣的。首先都是調用本地方法啟動一個線程,其次是在這個線程裡執行目標對象的run()方法。那麼這個目標對象是什麼呢?為了弄明白這個問題,我們來看看Thread類的run()方法的實現: 

public void run() {

if (target != null) {

target.run();

}

}

 

當我們採用實現Runnable介面的方式來實現線程的情況下,在調用new Thread(Runnable target)構造器時,將實現Runnable介面的類的執行個體設定成了線程要執行的主體所屬的目標對象target,當線程啟動時,這個執行個體的run()方法就被執行了。當我們採用繼承Thread的方式實現線程時,線程的這個run()方法被重寫了,所以當線程啟動時,執行的是這個對象自身的run()方法。總結起來就一句話,線程類有一個Runnable類型的target屬性,它是線程啟動後要執行的run()方法所屬的主體,如果我們採用的是繼承Thread類的方式,那麼這個target就是線程對象自身,如果我們採用的是實現Runnable介面的方式,那麼這個target就是實現了Runnable介面的類的執行個體。 

 

 

【線程的狀態 】

 

在Java 5.0及以上版本中,線程的狀態被擴充為建立、可運行、阻塞、等待、定時等待、死亡六種。

線程的狀態完全包含了一個線程從建立到運行,最後到結束的整個生命週期。

 

線程狀態的具體資訊如下: 

 

NEW(建立狀態、初始化狀態):

線程對象已經被建立,但是還沒有被啟動時的狀態。這段時間就是在我們調用new命令之後,調用start()方法之前。

 

RUNNABLE(可運行狀態、就緒狀態):

在我們調用了線程的start()方法之後線程所處的狀態。處於RUNNABLE狀態的線程在JAVA虛擬機器(JVM)上是運行著的,但是它可能還正在等待作業系統分配給它相應的運行資源以得以運行。

 

BLOCKED(阻塞狀態、被中斷運行):

線程正在等待其它的線程釋放同步鎖,以進入一個同步塊或者同步方法繼續運行;或者它已經進入了某個同步塊或同步方法,在啟動並執行過程中它調用了某個對象繼承自java.lang.Object的wait()方法,正在等待重新返回這個同步塊或同步方法。

 

WAITING(等待狀態):

當前線程調用了java.lang.Object.wait()、java.lang.Thread.join()或者java.util.concurrent.locks.LockSupport.park()三個中的任意一個方法,正在等待另外一個線程執行某個操作。比如一個線程調用了某個對象的wait()方法,正在等待其它線程調用這個對象的notify()或者notifyAll()(這兩個方法同樣是繼承自Object類)方法來喚醒它;或者一個線程調用了另一個線程的join()(這個方法屬於Thread類)方法,正在等待這個方法運行結束。

 

TIMED_WAITING(定時等待狀態):

當前線程調用了java.lang.Object.wait(long timeout)、java.lang.Thread.join(long millis)、java.util.concurrent.locks.LockSupport.packNanos(long nanos)、java.util.concurrent.locks.LockSupport.packUntil(long deadline)四個方法中的任意一個,進入等待狀態,但是與WAITING狀態不同的是,它有一個最大等待時間,即使等待的條件仍然沒有滿足,只要到了這個時間它就會自動醒來。

 

TERMINATED(死亡狀態、終止狀態):

線程完成執行後的狀態。線程執行完run()方法中的全部代碼,從該方法中退出,進入TERMINATED狀態。還有一種情況是run()在運行過程中拋出了一個異常,而這個異常沒有被程式捕獲,導致這個線程異常終止進入TERMINATED狀態。

 

在Java5.0及以上版本中,線程的全部六種狀態都以枚舉類型的形式定義在java.lang.Thread類中了,代碼如下: 

public enum State {

NEW,

RUNNABLE,

BLOCKED,

WAITING,

TIMED_WAITING,

TERMINATED;

}

 

sleep()和wait()的區別 

sleep()方法和wait()方法都成產生讓當前啟動並執行線程停止啟動並執行效果,這是它們的共同點。下面我們來詳細說說它們的不同之處。 

 

#####sleep()方法是本地方法,屬於Thread類,它有兩種定義: 

public static native void sleep(long millis) throws InterruptedException;

public static void sleep(long millis, int nanos) throws InterruptedException {

//other code

}

其中的參數millis代表毫秒數(千分之一秒),nanos代表納秒數(十億分之一秒)。

這兩個方法都可以讓調用它的線程沉睡(停止運行)指定的時間,到了這個時間,線程就會自動醒來,變為可運行狀態(RUNNABLE),但這並不表示它馬上就會被運行,因為線程調度機制恢複線程的運行也需要時間。

調用sleep()方法並不會讓線程釋放它所持有的同步鎖;而且在這期間它也不會阻礙其它線程的運行。

 

#####wait()方法也是本地方法,屬於Object類,有三個定義: 

public final void wait() throws InterruptedException {

//do something

}

public final native void wait(long timeout) throws InterruptedException;

public final void wait(long timeout, int nanos) throws InterruptedException {

//do something

}

 

wari()和wait(long timeout,int nanos)方法都是基於wait(long timeout)方法實現的。同樣地,timeout代表毫秒數,nanos代表納秒數。當調用了某個對象的wait()方法時,當前啟動並執行線程就會轉入等待狀態(WAITING),等待別的線程再次調用這個對象的notify()或者notifyAll()方法(這兩個方法也是本地方法)喚醒它,或者到了指定的最大等待時間,線程自動醒來。如果線程擁有某個或某些對象的同步鎖,那麼在調用了wait()後,這個線程就會釋放它持有的所有同步資源,而不限於這個被調用了wait()方法的對象。wait()方法同樣會被Thread類的interrupt()方法中斷,併產生一個InterruptedException異常,效果同sleep()方法被中斷一樣。 

 

【實現同步的方式】

同步是多線程中的重要概念。同步的使用可以保證在多線程啟動並執行環境中,程式不會產生設計之外的錯誤結果。同

步的實現方式有兩種,同步方法和同步塊,這兩種方式都要用到synchronized關鍵字。 

 

給一個方法增加synchronized修飾符之後就可以使它成為同步方法,這個方法可以是靜態方法和非靜態方法,但是不能是抽象類別的抽象方法,也不能是介面中的介面方法。下面代碼是一個同步方法的樣本: 

public synchronized void aMethod() {

// do something

}

public static synchronized void anotherMethod() {

// do something

}

 

線程在執行同步方法時是具有排它性的。當任意一個線程進入到一個對象的任意一個同步方法時,這個對象的所有同步方法都被鎖定了,在此期間,其他任何線程都不能訪問這個對象的任意一個同步方法,直到這個線程執行完它所調用的同步方法並從中退出,從而導致它釋放了該對象的同步鎖之後。在一個對象被某個線程鎖定之後,其他線程是可以訪問這個對象的所有非同步方法的。 

 

同步塊的形式雖然與同步方法不同,但是原理和效果是一致的。同步塊是通過鎖定一個指定的對象,來對同步塊中包含的代碼進行同步;而同步方法是對這個方法塊裡的代碼進行同步,而這種情況下鎖定的對象就是同步方法所屬的主體對象自身。

如果這個方法是靜態同步方法呢?那麼線程鎖定的就不是這個類的對象了,也不是這個類自身,而是這個類對應的java.lang.Class類型的對象。同步方法和同步塊之間的相互制約只限於同一個對象之間,所以靜態同步方法只受它所屬類的其它靜態同步方法的制約,而跟這個類的執行個體(對象)沒有關係。 

 

下面這段代碼示範了同步塊的實現方式: 

public void test() {<br />// 同步鎖<br />String lock = "LOCK";<br />// 同步塊<br />synchronized (lock) {<br />// do something<br />}<br />int i = 0;<br />// ...<br />}<br /> 

對於作為同步鎖的對象並沒有什麼特別要求,任意一個對象都可以。如果一個對象既有同步方法,又有同步塊,那麼當其中任意一個同步方法或者同步塊被某個線程執行時,這個對象就被鎖定了,其他線程無法在此時訪問這個對象的同步方法,也不能執行同步塊。 

 

 

【synchronized和Lock 】

上面分析了同步方法:給一個方法增加synchronized修飾符之後就可以使它成為同步方法。

Lock是一個介面,它位於Java 5.0新增的java.utils.concurrent包的子包locks中。concurrent包及其子包中的類都是用來處理多線程編程的。實現Lock介面的類具有與synchronized關鍵字同樣的功能,但是它更加強大一些。java.utils.concurrent.locks.ReentrantLock是較常用的實現了Lock介面的類。下面是ReentrantLock類的一個應用執行個體: 

private Lock lock = new ReentrantLock();<br />public void testLock() {<br />// 鎖定對象<br />lock.lock();<br />try {<br />// do something<br />} finally {<br />// 釋放對對象的鎖定<br />lock.unlock();<br />}<br />} 

 

lock()方法用於鎖定對象,unlock()方法用於釋放對對象的鎖定,他們都是在Lock介面中定義的方法。位於這兩個方法之間的代碼在被執行時,效果等同於被放在synchronized同步塊中。一般用法是將需要在lock()和unlock()方法之間執行的代碼放在try{}塊中,並且在finally{}塊中調用unlock()方法,這樣就可以保證即使在執行代碼拋出異常的情況下,對象的鎖也總是會被釋放,否則的話就會為死結的產生增加可能。 

 

使用synchronized關鍵字實現的同步,會把一個對象的所有同步方法和同步塊看做一個整體,只要有一個被某個線程調用了,其他的就無法被別的線程執行,即使這些方法或同步塊與被調用的代碼之間沒有任何邏輯關係,這顯然降低了程式的運行效率。而使用Lock就能夠很好地解決這個問題。我們可以把一個對象中按照邏輯關係把需要同步的方法或代碼進行分組,為每個組建立一個Lock類型的對象,對實現同步。那麼,當一個同步塊被執行時,這個線程只會鎖定與當前運行代碼相關的其他代碼最小集合,而並不影響其他線程對其餘同步代碼的調用執行。 

 

 

【關於死結 】

死結就是一個進程中的每個線程都在等待這個進程中的其他線程釋放所佔用的資源,從而導致所有線程都無法繼續執行的情況。死結是多線程編程中一個隱藏的陷阱,它經常發生在多個線程共用資源的時候。在實際開發中,死結一般隱藏的較深,不容易被發現,一旦死結現象發生,就必然會導致程式的癱瘓。因此必須避免它的發生。 

 

程式中必須同時滿足以下四個條件才會引發死結: 

互斥(Mutual exclusion):線程所使用的資源中至少有一個是不能共用的,它在同一時刻只能由一個線程使用。

持有與等待(Hold and wait):至少有一個線程已經持有了資源,並且正在等待擷取其他的線程所持有的資源。

非搶佔式(No pre-emption):如果一個線程已經持有了某個資源,那麼在這個線程釋放這個資源之前,別的線程不能把它搶奪過去使用。

迴圈等待(Circular wait):假設有N個線程在運行,第一個線程持有了一個資源,並且正在等待擷取第二個線程持有的資源,而第二個線程正在等待擷取第三個線程持有的資源,依此類推……第N個線程正在等待擷取第一個線程持有的資源,由此形成一個迴圈等待。

 

 

【線程池 】

線程池就像資料庫連接池一樣,是一個對象池。所有的對象池都有一個共同的目的,那就是為了提高對象的使用率,從而達到提高程式效率的目的。比如對於Servlet,它被設計為多線程的(如果它是單線程的,你就可以想象,當1000個人同時請求一個網頁時,在第一個人獲得請求結果之前,其它999個人都在鬱悶地等待),如果為每個使用者的每一次請求都建立一個新的線程對象來啟動並執行話,系統就會在建立線程和銷毀線程上耗費很大的開銷,大大降低系統的效率。因此,Servlet多線程機制背後有一個線程池在支援,線程池在初始化初期就建立了一定數量的線程對象,通過提高對這些對象的利用率,避免高頻率地建立對象,從而達到提高程式的效率的目的。 

 

下面實現一個最簡單的線程池,從中理解它的實現原理。為此我們定義了四個類,它們的用途及具體實現如下: 

Task(任務):這是個代表任務的抽象類別,其中定義了一個deal()方法,繼承Task抽象類別的子類需要實現這個方法,並把這個任務需要完成的具體工作在deal()方法編碼實現。線程池中的線程之所以被建立,就是為了執行各種各樣數量繁多的任務的,為了方便線程對任務的處理,我們需要用Task抽象類別來保證任務的具體工作統一放在deal()方法裡來完成,這樣也使代碼更加規範。 

Task的定義如下: 

public abstract class Task {<br />public enum State {<br />/* 建立 */NEW, /* 執行中 */RUNNING, /* 已完成 */FINISHED<br />}<br />// 任務狀態<br />private State state = State.NEW;<br />public void setState(State state) {<br />this.state = state;<br />}<br />public State getState() {<br />return state;<br />}<br />public abstract void deal();<br />} 

TaskQueue(任務隊列):在同一時刻,可能有很多任務需要執行,而程式在同一時刻只能執行一定數量的任務,當需要執行的任務數超過了程式所能承受的任務數時怎麼辦呢?這就有了先執行哪些任務,後執行哪些任務的規則。TaskQueue類就定義了這些規則中的一種,它採用的是FIFO(先進先出,英文名是First In First Out)的方式,也就是按照任務到達的先後順序執行。 

TaskQueue類的定義如下: 

import java.util.Iterator;<br />import java.util.LinkedList;<br />import java.util.List;<br />public class TaskQueue {<br />private List<Task> queue = new LinkedList<Task>();<br />// 添加一項任務<br />public synchronized void addTask(Task task) {<br />if (task != null) {<br />queue.add(task);<br />}<br />}<br />// 完成任務後將它從任務隊列中刪除<br />public synchronized void finishTask(Task task) {<br />if (task != null) {<br />task.setState(Task.State.FINISHED);<br />queue.remove(task);<br />}<br />}<br />// 取得一項待執行任務<br />public synchronized Task getTask() {<br />Iterator<Task> it = queue.iterator();<br />Task task;<br />while (it.hasNext()) {<br />task = it.next();<br />// 尋找一個建立的任務<br />if (Task.State.NEW.equals(task.getState())) {<br />// 把任務狀態置為運行中<br />task.setState(Task.State.RUNNING);<br />return task;<br />}<br />}<br />return null;<br />}<br />}<br /> 

addTask(Task task)方法用於當一個新的任務到達時,將它添加到任務隊列中。這裡使用了LinkedList類來儲存任務到達的先後順序。finishTask(Task task)方法用於任務被執行完畢時,將它從任務隊列中清除出去。getTask()方法用於取得當前要執行的任務。

TaskThread(執行任務的線程):它繼承自Thread類,專門用於執行任務隊列中的待執行任務。

public class TaskThread extends Thread {<br />// 該線程所屬的線程池<br />private ThreadPoolService service;<br />public TaskThread(ThreadPoolService tps) {<br />service = tps;<br />}<br />public void run() {<br />// 線上程池啟動並執行狀態下執行任務隊列中的任務<br />while (service.isRunning()) {<br />TaskQueue queue = service.getTaskQueue();<br />Task task = queue.getTask();<br />if (task != null) {<br />task.deal();<br />}<br />queue.finishTask(task);<br />}<br />}<br />} 

 

ThreadPoolService(線程池服務類):這是線程池最核心的一個類。它在被建立了時候就建立了幾個線程對象,但是這些線程並沒有啟動運行,但調用了start()方法啟動線程池服務時,它們才真正運行。stop()方法可以停止線程池服務,同時停止池中所有線程的運行。而runTask(Task task)方法是將一個新的待執行任務交與線程池來運行。 

ThreadPoolService類的定義如下: 

import java.util.ArrayList;<br />import java.util.List;<br />public class ThreadPoolService {<br />// 線程數<br />public static final int THREAD_COUNT = 5;<br />// 線程池狀態<br />private Status status = Status.NEW;<br />private TaskQueue queue = new TaskQueue();<br />public enum Status {<br />/* 建立 */NEW, /* 提供服務中 */RUNNING, /* 停止服務 */TERMINATED,<br />}<br />private List<Thread> threads = new ArrayList<Thread>();<br />public ThreadPoolService() {<br />for (int i = 0; i < THREAD_COUNT; i++) {<br />Thread t = new TaskThread(this);<br />threads.add(t);<br />}<br />}<br />// 啟動服務<br />public void start() {<br />this.status = Status.RUNNING;<br />for (int i = 0; i < THREAD_COUNT; i++) {<br />threads.get(i).start();<br />}<br />}<br />// 停止服務<br />public void stop() {<br />this.status = Status.TERMINATED;<br />}<br />// 是否正在運行<br />public boolean isRunning() {<br />return status == Status.RUNNING;<br />}<br />// 執行任務<br />public void runTask(Task task) {<br />queue.addTask(task);<br />}<br />protected TaskQueue getTaskQueue() {<br />return queue;<br />}<br />}<br /> 

完成了上面四個類,我們就實現了一個簡單的線程池。現在我們就可以使用它了,下面的代碼做了一個簡單的樣本: 

public class SimpleTaskTest extends Task {<br />@Override<br />public void deal() {<br />// do something<br />}<br />public static void main(String[] args) throws InterruptedException {<br />ThreadPoolService service = new ThreadPoolService();<br />service.start();<br />// 執行十次任務<br />for (int i = 0; i < 10; i++) {<br />service.runTask(new SimpleTaskTest());<br />}<br />// 睡眠1秒鐘,等待所有任務執行完畢<br />Thread.sleep(1000);<br />service.stop();<br />}<br />} 

 

當然,我們實現的是最簡單的,這裡只是為了示範線程池的實現原理。在實際應用中,根據情況的不同,可以做很多最佳化。比如: 

調整任務隊列的規則,給任務設定優先權,層級高的任務優先執行。

動態維護線程池,當待執行任務數量較多時,增加線程的數量,加快任務的執行速度;當任務較少時,回收一部分長期閑置的線程,減少對系統資源的消耗。

 

事實上Java5.0及以上版本已經為我們提供了線程池功能,無需再重新實現。這些類位於java.util.concurrent包中。 

 

Executors類提供了一組建立線程池對象的方法,常用的有一下幾個: 

public static ExecutorService newCachedThreadPool() {<br />// other code<br />}<br />public static ExecutorService newFixedThreadPool(int nThreads) {<br />// other code<br />}<br />public static ExecutorService newSingleThreadExecutor() {<br />// other code<br />} 

newCachedThreadPool()方法建立一個動態線程池,其中線程的數量會根據實際需要來建立和回收,適合於執行大量短期任務的情況;newFixedThreadPool(int nThreads)方法建立一個包含固定數量線程對象的線程池,nThreads代表要建立的線程數,如果某個線程在啟動並執行過程中因為異常而終止了,那麼一個新的線程會被建立和啟動來代替它;而newSingleThreadExecutor()方法則只線上程池中建立一個線程,來執行所有的任務。 

 

這三個方法都返回了一個ExecutorService類型的對象。實際上,ExecutorService是一個介面,它的submit()方法負責接收任務並交與線程池中的線程去運行。submit()方法能夠接受Callable和Runnable兩種類型的對象。它們的用法和區別如下: 

Runnable介面:繼承Runnable介面的類要實現它的run()方法,並將執行任務的代碼放入其中,run()方法沒有傳回值。適合於只做某種操作,不關心運行結果的情況。

Callable介面:繼承Callable介面的類要實現它的call()方法,並將執行任務的代碼放入其中,call()將任務的執行結果作為傳回值。適合於執行某種操作後,需要知道執行結果的情況。

 

無論是接收Runnable型參數,還是接收Callable型參數的submit()方法,都會返回一個Future(也是一個介面)類型的對象。該對象中包含了任務的執行情況以及結果。調用Future的boolean isDone()方法可以獲知任務是否執行完畢;調用Object get()方法可以獲得任務執行後的返回結果,如果此時任務還沒有執行完,get()方法會保持等待,直到相應的任務執行完畢後,才會將結果返回。 

 

我們用下面的一個例子來示範Java5.0中線程池的使用: 

import java.util.concurrent.*;<br />public class ExecutorTest {<br />public static void main(String[] args) throws InterruptedException,<br />ExecutionException {<br />ExecutorService es = Executors.newSingleThreadExecutor();<br />Future fr = es.submit(new RunnableTest());// 提交任務<br />Future fc = es.submit(new CallableTest());// 提交任務<br />// 取得傳回值並輸出<br />System.out.println((String) fc.get());<br />// 檢查任務是否執行完畢<br />if (fr.isDone()) {<br />System.out.println("執行完畢-RunnableTest.run()");<br />} else {<br />System.out.println("未執行完-RunnableTest.run()");<br />}<br />// 檢查任務是否執行完畢<br />if (fc.isDone()) {<br />System.out.println("執行完畢-CallableTest.run()");<br />} else {<br />System.out.println("未執行完-CallableTest.run()");<br />}<br />// 停止線程池服務<br />es.shutdown();<br />}<br />}<br />class RunnableTest implements Runnable {<br />public void run() {<br />System.out.println("已經執行-RunnableTest.run()");<br />}<br />}<br />class CallableTest implements Callable {<br />public Object call() {<br />System.out.println("已經執行-CallableTest.call()");<br />return "傳回值-CallableTest.call()";<br />}<br />}<br />運行結果:<br />已經執行-RunnableTest.run()<br />已經執行-CallableTest.call()<br />傳回值-CallableTest.call()<br />執行完畢-RunnableTest.run()<br />執行完畢-CallableTest.run() 

使用完線程池之後,需要調用它的shutdown()方法停止服務,否則其中的所有線程都會保持運行,程式不會退出。

 

 

 

本文出自 “臧圩人·海納百川” 部落格,轉載請與作者聯絡!

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.