Android performance optimization using the thread pool to process asynchronous tasks

Source: Internet
Author: User
Tags comparable

When it comes to threading, I think everyone is unfamiliar, because threads are used more or less in development, and there are usually two ways to create threads:

1、继承Thread类2、实现Runnable接口

Although both of these ways can create a thread, but there is a little difference between them, the main difference is that in the case of multi-threaded access to the same resource, the thread created with the Runnable interface can handle the same resource, and threads created with the thread class are handled independently, each with their own resources.

So, most multithreaded programs in Java are done by implementing runnable, and for Android it is no exception, when it comes to the need to open a thread to do something, we will write:

    new Thread(new Runnable() {        @Override        public void run() {            //do sth .        }    }).start();

This code creates a thread and executes it, and the GC automatically reclaims the thread after the task is finished, and it looks so wonderful, yes, it's really good in a program that doesn't have many threads, and if there's a lot of places in the program that require a lot of threading to handle tasks, then if you're going to create threading in the same way , then the performance of the system will be very bad, let alone in memory-constrained mobile devices, the main impact is as follows:

1、线程的创建和销毁都需要时间,当有大量的线程创建和销毁时,那么这些时间的消耗则比较明显,将导致性能上的缺失2、大量的线程创建、执行和销毁是非常耗cpu和内存的,这样将直接影响系统的吞吐量,导致性能急剧下降,如果内存资源占用的比较多,还很可能造成OOM3、大量的线程的创建和销毁很容易导致GC频繁的执行,从而发生内存抖动现象,而发生了内存抖动,对于移动端来说,最大的影响就是造成界面卡顿

The solution to the problem described above is to reuse existing threads, thereby reducing the creation of threads.
So this involves the concept of the thread pool (executorservice), the basic function of the thread pool is the reuse of threads, the following describes the use of the thread pool
Executorservice

Through the above analysis, we know the disadvantage of creating threads to handle tasks through the new thread () Start () mode, and in order to solve these problems, Java provides us with Executorservice line pool optimization and management thread usage
Advantages of using thread pooling to manage threads

1、线程的创建和销毁由线程池维护,一个线程在完成任务后并不会立即销毁,而是由后续的任务复用这个线程,从而减少线程的创建和销毁,节约系统的开销2、线程池旨在线程的复用,这就可以节约我们用以往的方式创建线程和销毁所消耗的时间,减少线程频繁调度的开销,从而节约系统资源,提高系统吞吐量3、在执行大量异步任务时提高了性能4、Java内置的一套ExecutorService线程池相关的api,可以更方便的控制线程的最大并发数、线程的定时任务、单线程的顺序执行等

Executorservice Introduction

Usually we talk about the thread pool The first thing that comes to mind is it: Executorservice, it's an interface, in fact, it can be called a thread pool service in the real sense, because it provides a number of interface APIs to control threads in the thread pool. The real thread pool is: Threadpoolexecutor, which implements the Executorservice interface and encapsulates a series of APIs that make it feature a thread pool, including the work queue, the number of core threads, the maximum number of threads, and so on.
Thread pool: Threadpoolexecutor

Since the thread pool is threadpoolexecutor, we need to create a thread pool that requires only the new Threadpoolexecutor (...); You can create a thread pool, and if you create a thread pool like this, we need to configure a bunch of things, very cumbersome, and we can look at how it's constructed:

Publicthreadpoolexecutor (Intcorepoolsize,
Intmaximumpoolsize,
Longkeepalivetime,
Timeunitunit,
Blockingqueueworkqueue,
Threadfactorythreadfactory,
Rejectedexecutionhandlerhandler) {...}

So, instead of using this method to create a thread pool, it is recommended to use Executors's factory method to create a thread pool, and the executors class is an official factory class that encapsulates a number of different thread pools, which makes it very easy to create a thread pool. The following five types of thread pools are mainly available:

1、newFixedThreadPool() :作用:该方法返回一个固定线程数量的线程池,该线程池中的线程数量始终不变,即不会再创建新的线程,也不会销毁已经创建好的线程,自始自终都是那几个固定的线程在工作,所以该线程池可以控制线程的最大并发数。栗子:假如有一个新任务提交时,线程池中如果有空闲的线程则立即使用空闲线程来处理任务,如果没有,则会把这个新任务存在一个任务队列中,一旦有线程空闲了,则按FIFO方式处理任务队列中的任务。2、newCachedThreadPool() :作用:该方法返回一个可以根据实际情况调整线程池中线程的数量的线程池。即该线程池中的线程数量不确定,是根据实际情况动态调整的。栗子:假如该线程池中的所有线程都正在工作,而此时有新任务提交,那么将会创建新的线程去处理该任务,而此时假如之前有一些线程完成了任务,现在又有新任务提交,那么将不会创建新线程去处理,而是复用空闲的线程去处理新任务。那么此时有人有疑问了,那这样来说该线程池的线程岂不是会越集越多?其实并不会,因为线程池中的线程都有一个“保持活动时间”的参数,通过配置它,如果线程池中的空闲线程的空闲时间超过该“保存活动时间”则立刻停止该线程,而该线程池默认的“保持活动时间”为60s。3、newSingleThreadExecutor() :作用:该方法返回一个只有一个线程的线程池,即每次只能执行一个线程任务,多余的任务会保存到一个任务队列中,等待这一个线程空闲,当这个线程空闲了再按FIFO方式顺序执行任务队列中的任务。4、newScheduledThreadPool() :作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。5、newSingleThreadScheduledExecutor() :作用:该方法返回一个可以控制线程池内线程定时或周期性执行某任务的线程池。只不过和上面的区别是该线程池大小为1,而上面的可以指定线程池的大小。

Well, write a bunch to introduce the role of these five thread pools, and then get these five kinds of thread pools, using Executors's factory method to get:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(5);ExecutorService singleThreadPool = Executors.newSingleThreadExecutor();ExecutorService cachedThreadPool = Executors.newCachedThreadPool();ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();

We can see how easy it is to create a thread pool through executors's factory method, in fact it's inside or through new Threadpoolexecutor (...). Way to create the thread pool, let's look at the internal implementation of these factory methods:

  Publicstaticexecutorservicenewfixedthreadpool (intnthreads) {Returnnewthreadpoolexecutor (nThreads, Nthreads, 0l,timeunit.milliseconds, Newlinkedblockingque UE ());} Publicstaticexecutorservicenewsinglethreadexecutor () {Returnnewfinalizabledelegatedexecutorservice (NewThreadPoo Lexecutor (0l,timeunit.milliseconds, Newlinkedblockingqu Eue ()));}                                  Publicstaticexecutorservicenewcachedthreadpool () {Returnnewthreadpoolexecutor (0,integer.max_value, 60l,timeunit.seconds, Newsynchronousqueue ());}  

We can clearly see that the internal implementations of these methods are created by creating a Threadpoolexecutor object, which is called original aim, so we need to understand the thread pool or the threadpoolexecutor thread pool class, Where the thread pool associated with the timed task is more special (Newscheduledthreadpool (), Newsinglethreadscheduledexecutor ()), The internal implementation of the thread pool that they create is implemented by the Scheduledthreadpoolexecutor class, and Scheduledthreadpoolexecutor is inherited from the threadpoolexecutor extension, So the essence is still the same, but more encapsulated some of the timing task related APIs, so we are mainly to understand threadpoolexecutor, starting from the construction method:

public ThreadPoolExecutor(int corePoolSize,                          int maximumPoolSize,                          long keepAliveTime,                          TimeUnit unit,                          BlockingQueue workQueue,                          ThreadFactory threadFactory,                          RejectedExecutionHandler handler) {//...}

We can see that the parameters of the constructor method are more, there are seven, the following hit illustrate the effect of these parameters:

corePoolSize:线程池中的核心线程数量maximumPoolSize:线程池中的最大线程数量keepAliveTime:这个就是上面说到的“保持活动时间“,上面只是大概说明了一下它的作用,不过它起作用必须在一个前提下,就是当线程池中的线程数量超过了corePoolSize时,它表示多余的空闲线程的存活时间,即:多余的空闲线程在超过keepAliveTime时间内没有任务的话则被销毁。而这个主要应用在缓存线程池中unit:它是一个枚举类型,表示keepAliveTime的单位,常用的如:TimeUnit.SECONDS(秒)、TimeUnit.MILLISECONDS(毫秒)workQueue:任务队列,主要用来存储已经提交但未被执行的任务,不同的线程池采用的排队策略不一样,稍后再讲threadFactory:线程工厂,用来创建线程池中的线程,通常用默认的即可handler:通常叫做拒绝策略,1、在线程池已经关闭的情况下 2、任务太多导致最大线程数和任务队列已经饱和,无法再接收新的任务 。在上面两种情况下,只要满足其中一种时,在使用execute()来提交新的任务时将会拒绝,而默认的拒绝策略是抛一个RejectedExecutionException异常

The above parameters are relatively simple to understand, but workqueue this task queue to explain again, it is a Blockingqueue object, and the generic is to limit it to store runnable object, just above said, Different thread pool its task queue implementation is certainly not the same, so, to ensure that different thread pool has different functions of the core is the implementation of this workqueue, careful will find in the just to create a thread pool in the factory method, for different thread pool incoming workqueue is not the same, Let me summarize what these five thread pools use separately Blockingqueue:

1、newFixedThreadPool()—>LinkedBlockingQueue2、newSingleThreadExecutor()—>LinkedBlockingQueue3、newCachedThreadPool()—>SynchronousQueue4、newScheduledThreadPool()—>DelayedWorkQueue5、newSingleThreadScheduledExecutor()—>DelayedWorkQueue

Each of these queues represents:

LinkedBlockingQueue:无界的队列SynchronousQueue:直接提交的队列DelayedWorkQueue:等待队列

Of course, the queue that implements the Blockingqueue interface also has the following: Arrayblockingqueue (bounded queue), Priorityblockingqueue (priority queue). The detailed role of these queues is not much to be introduced.
Use of thread pool Threadpoolexecutor

Use the thread pool, which involves an extremely important method, namely:
1

Execute (Runnablecommand)

This method is intended to perform a given task, which processes the threads that may be in the new thread, the pooled thread, or the thread being invoked, as determined by the Threadpoolexecutor implementation.
Newfixedthreadpool
Create a thread pool with a fixed number of threads, as an example:

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);for (int i = 1; i 10; i++) {    final int index = i;    fixedThreadPool.execute(new Runnable() {         @Override         public void run() {             String threadName = Thread.currentThread().getName();             Log.v("zxy", "线程:"+threadName+",正在执行第" + index + "个任务");             try {                    Thread.sleep(2000);             } catch (InterruptedException e) {                    e.printStackTrace();             }         }     }); }

In the code above, we created a thread pool with a fixed thread count of 3 threads, and the thread pool supported the maximum number of concurrent threads is 3, and I simulated 10 tasks to get it done, the first three tasks were executed, the next 7 went to the task queue to wait, and after the first three tasks were executed, The task execution is then taken from the task queue in a FIFO manner until the last task is completed.
In order to reflect the reuse of the thread, I specifically added the name of the current threads in the log, the effect is:
Write a picture description here
Newsinglethreadexecutor
Create a thread pool with only one threads, one thread at a time, and extra tasks to a task queue, waiting for the thread to finish processing and then processing the tasks in the task queue, for example:

    ExecutorServicesingleThreadPool=Executors.newSingleThreadExecutor();    for(inti=1;i10;i++){        finalintindex=i;        singleThreadPool.execute(newRunnable(){            @Override            publicvoidrun(){                StringthreadName=Thread.currentThread().getName();                Log.v("zxy","线程:"+threadName+",正在执行第"+index+"个任务");                try{                    Thread.sleep(1000);                }catch(InterruptedExceptione){                    e.printStackTrace();                }            }        });    }

The code is similar, but changed the implementation of the thread pool, the effect I think we all know, that is, one of the processing tasks, and all are reused a thread, the effect is: here to write the picture description

In fact, we found through the methods of Newsinglethreadexecutor () and Newfixedthreadpool (), Creating a singlethreadexecutorpool is actually creating a fixedthreadpool that has a core number of threads and a maximum number of threads of 1.
Newcachedthreadpool
Create a thread pool that can adjust the number of threads in the threading pool according to the actual situation, for example:

    ExecutorService cachedThreadPool = Executors.newCachedThreadPool();    for (int i = 1; i 10; i++) {        final int index = i;        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        cachedThreadPool.execute(new Runnable() {            @Override            public void run() {                String threadName = Thread.currentThread().getName();                Log.v("zxy", "线程:" + threadName + ",正在执行第" + index + "个任务");                try {                    long time = index * 500;                    Thread.sleep(time);                } catch (InterruptedException e) {                    e.printStackTrace();                }            }        });    }

In order to show that the thread pool can be automatically based on the implementation of threading reuse, rather than blindly create a new thread to handle the task, I set every 1s to submit a new task, the new task execution time is also dynamic change, so the effect is:
Write a picture description here
Newscheduledthreadpool
Create a thread pool that can perform tasks on a timed or periodic basis, as an example:

    ScheduledExecutorServicescheduledThreadPool=Executors.newScheduledThreadPool(3);    //延迟2秒后执行该任务    scheduledThreadPool.schedule(newRunnable(){        @Override        publicvoidrun(){        }    },2,TimeUnit.SECONDS);    //延迟1秒后,每隔2秒执行一次该任务    scheduledThreadPool.scheduleAtFixedRate(newRunnable(){        @Override        publicvoidrun(){        }    },1,2,TimeUnit.SECONDS);

Newsinglethreadscheduledexecutor
Create a thread pool that can perform tasks on a timed or periodic basis, with 1 threads in the thread pool, as an example:

    ScheduledExecutorService singleThreadScheduledPool = Executors.newSingleThreadScheduledExecutor();    //延迟1秒后,每隔2秒执行一次该任务    singleThreadScheduledPool.scheduleAtFixedRate(new Runnable() {        @Override        public void run() {            String threadName = Thread.currentThread().getName();            Log.v("zxy", "线程:" + threadName + ",正在执行");        }    },1,2,TimeUnit.SECONDS);

In fact, this is not much different from the above, but the number of threads in the thread pool, the effect is:
Write a picture description here
This task is performed every 2 seconds
Custom thread pool Threadpoolexecutor

Java built-in only provides us with five commonly used thread pools, which is generally sufficient, but sometimes we can also customize our own thread pool according to our needs, and to customize the thread pool of different functions. We also said that the thread pool function is different in the final analysis or internal blockingqueue implementation is different, so, we want to achieve our own thread pool, we must from the implementation of Blockingqueue to tamper with, And the above also said Blockingqueue implementation class has many, then this time we choose Priorityblockingqueue to implement a function is the priority of the task to handle the thread pool.
1, first we create a thread pool based on priorityblockingqueue implementation, for testing convenience, I set the number of core threads here to 3, as follows:
1

Executorserviceprioritythreadpool=newthreadpoolexecutor (3,3,0l,timeunit.seconds,newpriorityblockingqueue ());
2, then create a class to implement the Runnable interface, and provide an abstract method for us to implement the custom function, and implement the comparable interface, the implementation of this interface is mainly a priority comparison, the code is as follows:

Public abstract class Priorityrunnable implements Runnable, comparable {
private int priority;

public PriorityRunnable(int priority) {    if (priority 0)        throw new IllegalArgumentException();    this.priority = priority;}@Overridepublic int compareTo(PriorityRunnable another) {    int my = this.getPriority();    int other = another.getPriority();    return my 1 : my > other ? -1 : 0;}@Overridepublic void run() {    doSth();}public abstract void doSth();public int getPriority() {    return priority;}

}
3, the use of our own priorityrunnable submit tasks, the overall code is as follows:

    ExecutorServicepriorityThreadPool=newThreadPoolExecutor(3,3,0L,TimeUnit.SECONDS,newPriorityBlockingQueue());    for(inti=1;i10;i++){        finalintpriority=i;        priorityThreadPool.execute(newPriorityRunnable(priority){            @Override            publicvoiddoSth(){                StringthreadName=Thread.currentThread().getName();                Log.v("zxy","线程:"+threadName+",正在执行优先级为:"+priority+"的任务");                try{                    Thread.sleep(2000);                }catch(InterruptedExceptione){                    e.printStackTrace();                }            }        });    }

Test effect

Let's see if the thread pool we just defined has reached the function we want, which is to prioritize tasks based on the priority of the task, with the following effect: Write a picture here

As can be seen from the execution results, because the number of core threads is set to 3, at the beginning, the system has 3 idle threads, so instead of using the task queue, but directly run the first three tasks, and then submit the task because there is currently no idle thread to join the task queue to wait, at this time, Because our task queue implementation is implemented by Priorityblockingqueue, the waiting tasks are prioritized and prioritized and processed before the queue. You can also see that the next task is to perform a high-priority task first, then decrement it in turn.
Advantages of the priority thread pool

As we can learn from the above, it is very useful to create a priority thread pool, which can prioritize the tasks we want to handle first when the number of threads in the thread pools is insufficient or the system resources are tight, which greatly improves the system default thread pool's non-flexible handling of tasks in a FIFO manner.
Extension thread pool Threadpoolexecutor

In addition to the built-in features, the Threadpoolexecutor also provides three interfaces for us to expand our own thread pool to meet our needs, these three interfaces are:

beforeExecute() – 任务执行前执行的方法afterExecute() -任务执行结束后执行的方法terminated() -线程池关闭后执行的方法

These three methods are not implemented within the threadpoolexecutor.

The first two methods can be found in the Threadpoolexecutor internal Runworker () method, and Runworker () is the method of Threadpoolexecutor's inner class worker implementation, Worker it implements the Runnable interface, it is also the thread Cheng Processing task of the worker thread, and the Worker.runworker () method is to handle the task we submitted, it will be accessed by multiple threads at the same time, so we look at the implementation of the Runworker () method, Because asynchronous calls involving multiple threads are bound to be handled using locks, which are implemented using lock, let's take a look at the main implementation within the Runworker () method: Write a description of the picture here

You can see that the BeforeExecute and AfterExecute methods were called before and after Task.run () and passed in our task Runnable object

While terminated () is called in the method of shutting down the thread pool, there are two ways to close the thread pool, and I'll paste one of them: Write a description of the picture here

So, to expand the thread pool, we just need to rewrite these three methods and implement our own functionality, and these three methods will be called before the task executes, when the task executes, and when the thread pool is closed.
Here I verify, inherit from Threadpoolexecutor and implement those three methods:

public class Mythreadpoolexecutor extends Threadpoolexecutor {
Public mythreadpoolexecutor (int corepoolsize, int maximumpoolsize, long keepalivetime, timeunit unit, Blockingqueue WorkQueue) {
Super (Corepoolsize, Maximumpoolsize, KeepAliveTime, Unit, workQueue);
}

@Overrideprotected void beforeExecute(Thread t, Runnable r) {    super.beforeExecute(t, r);    String threadName = t.getName();    Log.v("zxy", "线程:" + threadName + "准备执行任务!");}@Overrideprotected void afterExecute(Runnable r, Throwable t) {    super.afterExecute(r, t);    String threadName = Thread.currentThread().getName();    Log.v("zxy", "线程:" + threadName + "任务执行结束!");}@Overrideprotected void terminated() {    super.terminated();    Log.v("zxy", "线程池结束!");}

}

The result of the run is that it is in line with what has just been said:

11-1705:47:51.1841602-1619/? V/zxy: Thread: Pool-6-thread-1 ready to perform the task!
11-1705:47:51.1841602-1619/? V/zxy: Thread: Pool-6-thread-1 is performing a task!
11-1705:47:53.1841602-1619/? V/zxy: Thread: Pool-6-thread-1 task execution ends!
11-1705:47:58.8961602-1619/? V/zxy: End of thread pool!

So, on the code of our priority thread pool above, we then extend a priority thread pool with pause, with the following code:
A thread pool with temporary functionality:

public class Pausablethreadpoolexecutor extends Threadpoolexecutor {
Private Boolean ispaused;
Private Reentrantlock Pauselock = new Reentrantlock ();
Private Condition unpaused = Pauselock.newcondition ();

public PausableThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue) {    super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);}@Overrideprotected void beforeExecute(Thread t, Runnable r) {    super.beforeExecute(t, r);    pauseLock.lock();    try {        while (isPaused) unpaused.await();    } catch (InterruptedException ie) {        t.interrupt();    } finally {        pauseLock.unlock();    }}public void pause() {    pauseLock.lock();    try {        isPaused = true;    } finally {        pauseLock.unlock();    }}public void resume() {    pauseLock.lock();    try {        isPaused = false;        unpaused.signalAll();    } finally {        pauseLock.unlock();    }}

}

Then, in conjunction with the implementation of the priority thread pool above, create a priority thread pool with pause capability:

    PausableThreadPoolExecutorpausableThreadPoolExecutor=newPausableThreadPoolExecutor(1,1,0L,TimeUnit.SECONDS,newPriorityBlockingQueue());    for(inti=1;i100;i++){        finalintpriority=i;        pausableThreadPoolExecutor.execute(newPriorityRunnable(priority){            @Override            publicvoiddoSth(){                runOnUiThread(newRunnable(){                    @Override                    publicvoidrun(){                        textView.setText(priority+"");                    }                });                try{                    Thread.sleep(1000);                }catch(InterruptedExceptione){                    e.printStackTrace();                }            }        });    }

Here I'm trying to demonstrate the effect of setting this thread pool to only one thread, then displaying the priority of the currently executing task directly in TextView, then setting a switch that controls the thread pool's pause and start:

    if (isPause) {        pausableThreadPoolExecutor.resume();        isPause = false;    } else {        pausableThreadPoolExecutor.pause();        isPause = true;    }

The effect is: Write a picture description here

In effect, the thread pool is the same as the priority thread, and there is one more pause and start function
Optimizing the thread pool Threadpoolexecutor

Although the thread pool greatly improves the performance of the system, but the creation of the thread pool is also required resources, so the size of thread pool threads will also affect the performance of the system, big instead of wasting resources, small but affect the throughput of the system, so we create a thread pool need to grasp a degree to play its advantages reasonably In general, we need to consider factors such as the number of CPUs, the size of the memory, the number of concurrent requests, and other factors, adjusted as needed.

Typically, the number of core threads can be set to the number of CPUs +1, and the maximum number of threads can be set to the number of CPUs *2+1.

The method to get the number of CPUs is:
1

Runtime.getruntime (). Availableprocessors ();
The difference between shutdown () and Shutdownnow ()

About the thread pool stop, Executorservice provides us with two methods: Shutdown and Shutdownnow, these two methods are different, can be based on the actual needs of the convenient use, as follows:

1、shutdown()方法在终止前允许执行以前提交的任务。2、shutdownNow()方法则是阻止正在任务队列中等待任务的启动并试图停止当前正在执行的任务。

On the implementation of Asynctask

We all know that asynctask internal realization is actually thread+handler. Where handler is to handle communication between threads, and what exactly does this thread mean? Through the Asynctask source can be learned, in fact, this thread is the thread pool, asynctask internal implementation of the two thread pool, namely: serial thread pools and the number of fixed threads. The number of fixed threads is determined by the number of CPUs.

By default, most of us perform tasks through Asynctask::execute (),
, and execute () internally calls the Executeonexecutor (Sdefaultexecutor, params) method, and the first parameter is the thread pool that specifies the task to be processed. By default, Asynctask is passed in to the serial thread pool (where the version changes are not spoken here), that is, the task can only be executed in a single order, and if we want to make asynctask parallel to the processing task, we all know that calling Asynctask: The Executeonexecutor (Sdefaultexecutor, params) method passes this parameter: Asynctask.thread_pool_executor.
The significance of this parameter is to specify a fixed number of threads for the task of the thread pool to deal with, so as to achieve the function of parallel processing, we can see in the source code asynctask.thread_pool_executor This parameter is a fixed number of threads of the thread pool:

publicstaticfinalExecutorTHREAD_POOL_EXECUTOR        =newThreadPoolExecutor(CORE_POOL_SIZE,MAXIMUM_POOL_SIZE,KEEP_ALIVE,                TimeUnit.SECONDS,sPoolWorkQueue,sThreadFactory);

Android performance optimization using the thread pool to process asynchronous tasks

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

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.