android多线程-AsyncTask之工作原理深入解析(下)

android多线程-AsyncTask之工作原理深入解析(下)

关联文章:
Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

上篇分析AsyncTask的一些基本用法以及不同android版本下的区别,接着本篇我们就来全面剖析一下AsyncTask的工作原理。在开始之前我们先来了解一个多线程的知识点——Callable<V> 、Future<V>和FutureTask类

一、理解Callable<V> 、Future<V>以及FutureTask类

Callable<V>

Callable的接口定义如下:

public interface Callable<V> {   
      V   call()   throws Exception;   
}   

 

Callable接口声明了一个名称为call()的方法,该方法可以有返回值V,也可以抛出异常。Callable也是一个线程接口,它与Runnable的主要区别就是Callable在线程执行完成后可以有返回值而Runnable没有返回值,Runnable接口声明如下:

public interface Runnable {
    public abstract void run();
}

 

那么Callable接口如何使用呢,Callable需要和ExcutorService结合使用,其中ExecutorService也是一个线程池对象继承自Executor接口,对于线程池的知识点不了解可以看看我的另一篇文章,这里就不深入了,接着看看ExecutorService提供了那些方法供我们使用:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

 

  • submit(Callable task),传递一个实现Callable接口的任务,并且返回封装了异步计算结果的Future。
  • submit(Runnable task, T result),传递一个实现Runnable接口的任务,并且指定了在调用Future的get方法时返回的result对象。
  • submit(Runnable task),传递一个实现Runnable接口的任务,并且返回封装了异步计算结果的Future。

因此我们只要创建好我们的线程对象(实现Callable接口或者Runnable接口),然后通过上面3个方法提交给线程池去执行即可。Callable接口介绍就先到这,再来看看Future时什么鬼。

Future<V>

Future接口是用来获取异步计算结果的,说白了就是对具体的Runnable或者Callable对象任务执行的结果进行获取(get()),取消(cancel()),判断是否完成等操作。其方法如下:

public interface Future<V> {
    //取消任务
    boolean cancel(boolean mayInterruptIfRunning);

    //如果任务完成前被取消,则返回true。
    boolean isCancelled();

    //如果任务执行结束,无论是正常结束或是中途取消还是发生异常,都返回true。
    boolean isDone();

    //获取异步执行的结果,如果没有结果可用,此方法会阻塞直到异步计算完成。
    V get() throws InterruptedException, ExecutionException;

    // 获取异步执行结果,如果没有结果可用,此方法会阻塞,但是会有时间限制,
    //如果阻塞时间超过设定的timeout时间,该方法将返回null。
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

}

 

总得来说Future有以下3点作用:

  • 能够中断执行中的任务
  • 判断任务是否执行完成
  • 获取任务执行完成后额结果。

但是Future只是接口,我们根本无法将其创建为对象,于官方又给我们提供了其实现类FutureTask,这里我们要知道前面两个接口的介绍都只为此类做铺垫,毕竟AsncyTask中使用到的对象是FutureTask。

FutureTask

先来看看FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V> {  
  • 1

显然FutureTask类实现了RunnableFuture接口,我们再看一下RunnableFuture接口的实现:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}
  • 1
  • 2
  • 3

从接口实现可以看出,FutureTask除了实现了Future接口外还实现了Runnable接口,因此FutureTask既可以当做Future对象也可是Runnable对象,当然FutureTask也就可以直接提交给线程池来执行。接着我们*关心的是如何创建FutureTask对象,实际上可以通过如下两个构造方法来构建FutureTask

public FutureTask(Callable<V> callable) {  
}  
public FutureTask(Runnable runnable, V result) {  
}  

 

从构造方法看出,我们可以把一个实现了Callable或者Runnable的接口的对象封装成一个FutureTask对象,然后通过线程池去执行,那么具体如何使用呢?简单案例,CallableDemo.java代码如下:


package com.zejian.Executor;
import java.util.concurrent.Callable;
/**
 * Callable接口实例 计算累加值大小并返回
 */
public class CallableDemo implements Callable<Integer> {

    private int sum;
    @Override
    public Integer call() throws Exception {
        System.out.println("Callable子线程开始计算啦!");
        Thread.sleep(2000);

        for(int i=0 ;i<5000;i++){
            sum=sum+i;
        }
        System.out.println("Callable子线程计算结束!");
        return sum;
    }
}

 

CallableTest.java测试代码如下:

package com.zejian.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
public class CallableTest {

public static void main(String[] args) {
//*种使用方式
//      //创建线程池
//      ExecutorService es = Executors.newSingleThreadExecutor();
//      //创建Callable对象任务
//      CallableDemo calTask=new CallableDemo();
//      //提交任务并获取执行结果
//      Future<Integer> future =es.submit(calTask);
//      //关闭线程池
//      es.shutdown();

    //第二中使用方式

    //创建线程池
    ExecutorService es = Executors.newSingleThreadExecutor();
    //创建Callable对象任务
    CallableDemo calTask=new CallableDemo();
    //创建FutureTask
    FutureTask<Integer> futureTask=new FutureTask<>(calTask);
    //执行任务
    es.submit(futureTask);
    //关闭线程池
    es.shutdown();
    try {
        Thread.sleep(2000);
    System.out.println("主线程在执行其他任务");

    if(futureTask.get()!=null){
        //输出获取到的结果
        System.out.println("futureTask.get()-->"+futureTask.get());
    }else{
        //输出获取到的结果
        System.out.println("futureTask.get()未获取到结果");
    }

    } catch (Exception e) {
        e.printStackTrace();
    }
    System.out.println("主线程在执行完成");
}
}

 

代码非常简单,注释也很明朗,这里我们分析一下第2种执行方式,先前声明一个CallableDemo类,该类实现了Callable接口,接着通过call方法去计算sum总值并返回。然后在测试类CallableTest中,把CallableDemo实例类封装成FutureTask对象并交给线程池去执行,*终执行结果将封装在FutureTask中,通过FutureTask#get()可以获取执行结果。*种方式则是直接把Callable实现类丢给线程池执行,其结果封装在Future实例中,第2种方式执行结果如下:

Callable子线程开始计算啦!
主线程在执行其他任务
Callable子线程计算结束!
futureTask.get()-->12497500
主线程在执行完成

 

ok~,到此我们对Callable、Future和FutureTask就介绍到这,有了这个知识铺垫,我们就可以愉快的撩开AsyncTask的内部工作原理了。

二、AsyncTask的工作原理完全解析

在上篇中,使用了如下代码来执行AsyncTask的异步任务:

new AysnTaskDiff("AysnTaskDiff-1").execute("");
  • 1

从代码可知,入口是execute方法,那我们就先看看execute的源码:

    @MainThread
    public final AsyncTask<Params, Progress, Result> execute(Params... params) {
        return executeOnExecutor(sDefaultExecutor, params);
    }

 

很明显execute方法只是一个壳子,直接调用了executeOnExecutor(sDefaultExecutor, params),其中sDefaultExecutor是一个串行的线程池,接着看看sDefaultExecutor内部实现:

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;
/**
 * An {@link Executor} that executes tasks one at a time in serial
 * order.  This serialization is global to a particular process.
 */
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();

//串行线程池类,实现Executor接口
private static class SerialExecutor implements Executor {
    final ArrayDeque<Runnable> mTasks = new ArrayDeque<Runnable>();
    Runnable mActive;

    public synchronized void execute(final Runnable r) {
        mTasks.offer(new Runnable() { //插入一个Runnble任务
            public void run() {
                try {
                    r.run();
                } finally {
                    scheduleNext();
                }
            }
        });
        //判断是否有Runnable在执行,没有就调用scheduleNext方法
        if (mActive == null) {
            scheduleNext();
        }
    }

    protected synchronized void scheduleNext() {
      //从任务队列mTasks中取出任务并放到THREAD_POOL_EXECUTOR线程池中执行.
      //由此也可见任务是串行进行的。
        if ((mActive = mTasks.poll()) != null) {
            THREAD_POOL_EXECUTOR.execute(mActive);
        }
    }
}

 

从源码可以看出,ArrayDeque是一个存放任务队列的容器(mTasks),任务Runnable传递进来后交给SerialExecutor的execute方法处理,SerialExecutor会把任务Runnable插入到任务队列mTasks尾部,接着会判断是否有Runnable在执行,没有就调用scheduleNext方法去执行下一个任务,接着交给THREAD_POOL_EXECUTOR线程池中执行,由此可见SerialExecutor并不是真正的线程执行者,它只是是保证传递进来的任务Runnable(实例是一个FutureTask)串行执行,而真正执行任务的是THREAD_POOL_EXECUTOR线程池,当然该逻辑也体现AsyncTask内部的任务是默认串行进行的。顺便看一下THREAD_POOL_EXECUTOR线程池的声明:

//CUP核数
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
//核心线程数量
private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
//*大线程数量
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
//非核心线程的存活时间1s
private static final int KEEP_ALIVE = 1;
//线程工厂类
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
    private final AtomicInteger mCount = new AtomicInteger(1);

    public Thread newThread(Runnable r) {
        return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
    }
};
//线程队列,核心线程不够用时,任务会添加到该队列中,队列满后,会去调用非核心线程执行任务
private static final BlockingQueue<Runnable> sPoolWorkQueue =
        new LinkedBlockingQueue<Runnable>(128);

/**
 * An {@link Executor} that can be used to execute tasks in parallel.
 * 创建线程池
 */
public static final Executor THREAD_POOL_EXECUTOR
        = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
                TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

 

ok~,关于sDefaultExecutor,我们先了解到这,回到之前execute方法内部调用的executeOnExecutor方法的步骤,先来看看executeOnExecutor都做了些什么事?其源码如下:

public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
            Params... params) {
   //判断在那种状态
   if (mStatus != Status.PENDING) {
       switch (mStatus) {
           case RUNNING:
               throw new IllegalStateException("Cannot execute task:"
                       + " the task is already running.");
           case FINISHED://只能执行一次!
               throw new IllegalStateException("Cannot execute task:"
                       + " the task has already been executed "
                       + "(a task can be executed only once)");
       }
   }

   mStatus = Status.RUNNING;
   //onPreExecute()在此执行了!!!
   onPreExecute();
   //参数传递给了mWorker.mParams
   mWorker.mParams = params;
   //执行mFuture任务,其中exec就是传递进来的sDefaultExecutor
   //把mFuture交给线程池去执行任务
   exec.execute(mFuture);

   return this;
    }

 

从executeOnExecutor方法的源码分析得知,执行任务前先会去判断当前AsyncTask的状态,如果处于RUNNING和FINISHED状态就不可再执行,直接抛出异常,只有处于Status.PENDING时,AsyncTask才会去执行。然后onPreExecute()被执行的,该方法可以用于线程开始前做一些准备工作。接着会把我们传递进来的参数赋值给 mWorker.mParams ,并执行开始执行mFuture任务,那么mWorker和mFuture到底是什么?先看看mWorker即WorkerRunnable的声明源码:

//抽象类
private static abstract class WorkerRunnable<Params, Result> implements Callable<Result> {

    Params[] mParams;

}

 

WorkerRunnable抽象类实现了Callable接口,因此WorkerRunnable本质上也算一个Callable对象,其内部还封装了一个mParams的数组参数,因此我们在外部执行execute方法时传递的可变参数*终会赋值给WorkerRunnable的内部数组mParams,这些参数*后会传递给doInBackground方法处理,这时我们发现doInBackground方法也是在WorkerRunnable的call方法中被调用的,看看其源码如下:

public AsyncTask() {
   //创建WorkerRunnable mWorker,本质上就是一个实现了Callable接口对象
    mWorker = new WorkerRunnable<Params, Result>() {
        public Result call() throws Exception {
            //设置标志
            mTaskInvoked.set(true);

         Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
            //执行doInBackground,并传递mParams参数
            Result result = doInBackground(mParams);
            Binder.flushPendingCommands();
            //执行完成调用postResult方法更新结果
            return postResult(result);
        }
    };
//把mWorker(即Callable实现类)封装成FutureTask实例
//*终执行结果也就封装在FutureTask中
    mFuture = new FutureTask<Result>(mWorker) {
        //任务执行完成后被调用
        @Override
        protected void done() {
            try {
             //如果还没更新结果通知就执行postResultIfNotInvoked
                postResultIfNotInvoked(get());
            } catch (InterruptedException e) {
                android.util.Log.w(LOG_TAG, e);
            } catch (ExecutionException e) {
                throw new RuntimeException("An error occurred while executing doInBackground()",
                        e.getCause());
            } catch (CancellationException e) {
                //抛异常
                postResultIfNotInvoked(null);
            }
        }
    };
}

 

可以看到在初始化AsyncTask时,不仅创建了mWorker(本质实现了Callable接口的实例类)而且也创建了FutureTask对象,并把mWorker对象封装在FutureTask对象中,*后FutureTask对象将在executeOnExecutor方法中通过线程池去执行。给出下图协助理解:
这里写图片描述

AsynTask在初始化时会创建mWorker实例对象和FutureTask实例对象,mWorker是一个实现了Callable线程接口并封装了传递参数的实例对象,然后mWorker实例会被封装成FutureTask实例中。在AsynTask创建后,我们调用execute方法去执行异步线程,其内部又直接调用了executeOnExecutor方法,并传递了线程池exec对象和执行参数,该方法内部通过线程池exec对象去执行mFuture实例,这时mWorker内部的call方法将被执行并调用doInBackground方法,*终通过postResult去通知更新结果。关于postResult方法,其源码如下:

private Result postResult(Result result) {
    @SuppressWarnings("unchecked")
    Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT,
            new AsyncTaskResult<Result>(this, result));
    message.sendToTarget();
    return result;
}

 

显然是通过Handler去执行结果更新的,在执行结果成返回后,会把result封装到一个AsyncTaskResult对象中,*后把MESSAGE_POST_RESULT标示和AsyncTaskResult存放到Message中并发送给Handler去处理,这里我们先看看AsyncTaskResult的源码:

private static class AsyncTaskResult<Data> {
        final AsyncTask mTask;
        final Data[] mData;

        AsyncTaskResult(AsyncTask task, Data... data) {
            mTask = task;
            mData = data;
        }
    }

 

显然AsyncTaskResult封装了执行结果的数组以及AsyncTask本身,这个没什么好说的,接着看看AsyncTaskResult被发送到handler后如何处理的。

private static class InternalHandler extends Handler {
    public InternalHandler() {
        //获取主线程的Looper传递给当前Handler,这也是为什么AsyncTask只能在主线程创建并执行的原因
        super(Looper.getMainLooper());
    }

    @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
    @Override
    public void handleMessage(Message msg) {
    //获取AsyncTaskResult
        AsyncTaskResult<?> result = (AsyncTaskResult<?>) msg.obj;
        switch (msg.what) {
            //执行完成
            case MESSAGE_POST_RESULT:
                // There is only one result
                result.mTask.finish(result.mData[0]);
                break;
                //更新进度条的标志
            case MESSAGE_POST_PROGRESS:
            //执行onProgressUpdate方法,自己实现。
                result.mTask.onProgressUpdate(result.mData);
                break;
        }
    }
}

 

从Handler的源码分析可知,该handler绑定的线程为主线线程,这也就是为什么AsyncTask必须在主线程创建并执行的原因了。接着通过handler发送过来的不同标志去决定执行那种结果,如果标示为MESSAGE_POST_RESULT则执行AsyncTask的finish方法并传递执行结果给该方法,finish方法源码如下:

private void finish(Result result) {
        if (isCancelled()) {//判断任务是否被取消
            onCancelled(result);
        } else {//执行onPostExecute(result)并传递result结果
            onPostExecute(result);
        }
        //更改AsyncTask的状态为已完成
        mStatus = Status.FINISHED;
    }

 

该方法先判断任务是否被取消,如果没有被取消则去执行onPostExecute(result)方法,外部通过onPostExecute方法去更新相关信息,如UI,消息通知等。*后更改AsyncTask的状态为已完成。到此AsyncTask的全部流程执行完。
这里还有另一种标志MESSAGE_POST_PROGRESS,该标志是我们在doInBackground方法中调用publishProgress方法时发出的,该方法原型如下:

protected final void publishProgress(Progress... values) {
    if (!isCancelled()) {
    //发送MESSAGE_POST_PROGRESS,通知更新进度条
        getHandler().obtainMessage(MESSAGE_POST_PROGRESS,
                new AsyncTaskResult<Progress>(this, values)).sendToTarget();
    }
}

 

ok~,AsyncTask的整体流程基本分析完,*后来个总结吧:当我们调用execute(Params… params)方法后,其内部直接调用executeOnExecutor方法,接着onPreExecute()被调用方法,执行异步任务的WorkerRunnable对象(实质为Callable对象)*终被封装成FutureTask实例,FutureTask实例将由线程池sExecutor执行去执行,这个过程中doInBackground(Params… params)将被调用(在WorkerRunnable对象的call方法中被调用),如果我们覆写的doInBackground(Params… params)方法中调用了publishProgress(Progress… values)方法,则通过InternalHandler实例sHandler发送一条MESSAGE_POST_PROGRESS消息,更新进度,sHandler处理消息时onProgressUpdate(Progress… values)方法将被调用;*后如果FutureTask任务执行成功并返回结果,则通过postResult方法发送一条MESSAGE_POST_RESULT的消息去执行AsyncTask的finish方法,在finish方法内部onPostExecute(Result result)方法被调用,在onPostExecute方法中我们可以更新UI或者释放资源等。这既是AsyncTask内部的工作流程,可以说是Callable+FutureTask+Executor+Handler内部封装。结尾我们献上一张执行流程,协助大家理解整个流程:
这里写图片描述
好~,本篇到此结束。。。

android多线程-AsyncTask之工作原理深入解析(上)

android多线程-AsyncTask之工作原理深入解析(上)

关联文章:
Android 多线程之HandlerThread 完全详解
Android 多线程之IntentService 完全详解
android多线程-AsyncTask之工作原理深入解析(上)
android多线程-AsyncTask之工作原理深入解析(下)

前两篇我们分析android的异步线程类HandlerThread与IntentService,它们都是android系统独有的线程类,而android中还有另一个比较重要的异步线程类,它就是AsyncTask。本篇我们将从以下3点深入分析AsyncTask。

  • AsyncTask的常规使用分析以及案例实现
  • AsyncTask在不同android版本的下的差异
  • AsyncTask的工作原理流程

一、AsyncTask的常规使用分析以及案例实现

AsyncTask是一种轻量级的异步任务类,它可以在线程池中执行后台任务,然后会把执行的进度和*终结果传递给主线程并更新UI。AsyncTask本身是一个抽象类它提供了Params、Progress、Result 三个泛型参数,其类声明如下:

public abstract class AsyncTask<Params, Progress, Result> {

由类声明可以看出AsyncTask抽象类确实定义了三种泛型类型 Params,Progress和Result,它们分别含义如下:

  • Params :启动任务执行的输入参数,如HTTP请求的URL
  • Progress : 后台任务执行的百分比
  • Result :后台执行任务*终返回的结果类型

如果AsyncTask不需要传递具体参数,那么这三个泛型参数可以使用Void代替。好~,我们现在创建一个类继承自AsyncTask如下:

package com.zejian.handlerlooper;

import android.graphics.Bitmap;
import android.os.AsyncTask;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:
 */
public class DownLoadAsyncTask extends AsyncTask<String,Integer,Bitmap> {

    /**
     * onPreExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行之前,该方法将会被调用
     * 一般用来在执行后台任务前对UI做一些标记和准备工作,
     * 如在界面上显示一个进度条。
     */
    @Override
    protected void onPreExecute() {
        super.onPreExecute();
    }

    /**
     * 抽象方法必须覆写,执行异步任务的方法
     * @param params
     * @return
     */
    @Override
    protected Bitmap doInBackground(String... params) {
        return null;
    }

    /**
     * onProgressUpdate是可以选择性覆写的方法
     * 在主线程中执行,当后台任务的执行进度发生改变时,
     * 当然我们必须在doInBackground方法中调用publishProgress()
     * 来设置进度变化的值
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
    }

    /**
     * onPostExecute是可以选择性覆写的方法
     * 在主线程中执行,在异步任务执行完成后,此方法会被调用
     * 一般用于更新UI或其他必须在主线程执行的操作,传递参数bitmap为
     * doInBackground方法中的返回值
     * @param bitmap
     */
    @Override
    protected void onPostExecute(Bitmap bitmap) {
        super.onPostExecute(bitmap);
    }

    /**
     * onCancelled是可以选择性覆写的方法
     * 在主线程中,当异步任务被取消时,该方法将被调用,
     * 要注意的是这个时onPostExecute将不会被执行
     */
    @Override
    protected void onCancelled() {
        super.onCancelled();
    }
}

 

如代码所示,我们创建一个继承自AsyncTask的异步线程类,在泛型参数方面,传递String类型(Url) , Integer类型(显示进度),Bitmap类型作为返回值。接着重写了抽象方法doInBackground(),以及覆写了onPreExecute()、onProgressUpdate()、onPostExecute()、onCancelled()等方法,它们的主要含义如下:

  • (1)onPreExecute(), 该方法在主线程中执行,将在execute(Params… params)被调用后执行,一般用来做一些UI的准备工作,如在界面上显示一个进度条。
  • (2)doInBackground(Params…params), 抽象方法,必须实现,该方法在线程池中执行,用于执行异步任务,将在onPreExecute方法执行后执行。其参数是一个可变类型,表示异步任务的输入参数,在该方法中还可通过publishProgress(Progress… values)来更新实时的任务进度,而publishProgress方法则会调用onProgressUpdate方法。此外doInBackground方法会将计算的返回结果传递给onPostExecute方法。
  • (3)onProgressUpdate(Progress…),在主线程中执行,该方法在publishProgress(Progress… values)方法被调用后执行,一般用于更新UI进度,如更新进度条的当前进度。
  • (4)onPostExecute(Result), 在主线程中执行,在doInBackground 执行完成后,onPostExecute 方法将被UI线程调用,doInBackground 方法的返回值将作为此方法的参数传递到UI线程中,并执行一些UI相关的操作,如更新UI视图。
  • (5)onCancelled(),在主线程中执行,当异步任务被取消时,该方法将被调用,要注意的是这个时onPostExecute将不会被执行。

我们这里再强调一下它们的执行顺序,onPreExecute方法先执行,接着是doInBackground方法,在doInBackground中如果调用了publishProgress方法,那么onProgressUpdate方法将会被执行,*后doInBackground方法执行后完后,onPostExecute方法将被执行。说了这么多,我们还没说如何启动AsyncTask呢,其实可以通过execute方法启动异步线程,其方法声明如下:

public final AsyncTask<Params, Progress, Result> execute(Params... params)
  • 1

该方法是一个final方法,参数类型是可变类型,实际上这里传递的参数和doInBackground(Params…params)方法中的参数是一样的,该方法*终返回一个AsyncTask的实例对象,可以使用该对象进行其他操作,比如结束线程之类的。启动范例如下:

new DownLoadAsyncTask().execute(url1,url2,url3);
  • 1

当然除了以上介绍的内容外,我们在使用AsyncTask时还必须遵守一些规则,以避免不必要的麻烦。

  • (1) AsyncTask的实例必须在主线程(UI线程)中创建 ,execute方法也必须在主线程中调用
  • (2) 不要在程序中直接的调用onPreExecute(), onPostExecute(Result),doInBackground(Params…), onProgressUpdate(Progress…)这几个方法
  • (3) 不能在doInBackground(Params… params)中更新UI
  • (5) 一个AsyncTask对象只能被执行一次,也就是execute方法只能调用一次,否则多次调用时将会抛出异常

到此,AsyncTask的常规方法说明和使用以及注意事项全部介绍完了,下面我们来看一个下载案例,该案例是去下载一张大图,并实现下载实时进度。先来看看AsynTaskActivity.java的实现:

package com.zejian.handlerlooper;

import android.content.Context;
import android.os.AsyncTask;
import android.os.Environment;
import android.os.PowerManager;
import android.widget.Toast;

import com.zejian.handlerlooper.util.LogUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:
 */
public class DownLoadAsyncTask extends AsyncTask<String, Integer, String> {
    private PowerManager.WakeLock mWakeLock;
    private int ValueProgress=100;
    private Context context;


    public DownLoadAsyncTask(Context context){
        this.context=context;
    }

    /**
     * sync method which download file
     * @param params
     * @return
     */
    @Override
    protected String doInBackground(String... params) {
        InputStream input = null;
        OutputStream output = null;
        HttpURLConnection connection = null;
        try {
            URL url = new URL(params[0]);
            connection = (HttpURLConnection) url.openConnection();
            connection.connect();
            // expect HTTP 200 OK, so we don't mistakenly save error report
            // instead of the file
            if (connection.getResponseCode() != HttpURLConnection.HTTP_OK) {
                return "Server returned HTTP " + connection.getResponseCode()
                        + " " + connection.getResponseMessage();
            }
            // this will be useful to display download percentage
            // might be -1: server did not report the length
            int fileLength = connection.getContentLength();
            // download the file
            input = connection.getInputStream();
            //create output
            output = new FileOutputStream(getSDCardDir());
            byte data[] = new byte[4096];
            long total = 0;
            int count;
            while ((count = input.read(data)) != -1) {
                // allow canceling with back button
                if (isCancelled()) {
                    input.close();
                    return null;
                }
                total += count;
                // publishing the progress....
                if (fileLength > 0) // only if total length is known
                    publishProgress((int) (total * 100 / fileLength));
                //
                Thread.sleep(100);
                output.write(data, 0, count);
            }
        } catch (Exception e) {
            return e.toString();
        } finally {
            try {
                if (output != null)
                    output.close();
                if (input != null)
                    input.close();
            } catch (IOException ignored) {
            }
            if (connection != null)
                connection.disconnect();
        }

        return null;
    }

    @Override
    protected void onPreExecute() {
        super.onPreExecute();
        // take CPU lock to prevent CPU from going off if the user
        // presses the power button during download
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
        mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                getClass().getName());
        mWakeLock.acquire();
        //Display progressBar
//        progressBar.setVisibility(View.VISIBLE);
    }

    @Override
    protected void onPostExecute(String values) {
        super.onPostExecute(values);
        mWakeLock.release();
        if (values != null)
            LogUtils.e("Download error: "+values);
        else {
            Toast.makeText(context, "File downloaded", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * set progressBar
     * @param values
     */
    @Override
    protected void onProgressUpdate(Integer... values) {
        super.onProgressUpdate(values);
//        progressBar.setmProgress(values[0]);
        //update progressBar
        if(updateUI!=null){
            updateUI.UpdateProgressBar(values[0]);
        }
    }

    /**
     * get SD card path
     * @return
     */
    public File getSDCardDir(){
        if(Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())){
            // 创建一个文件夹对象,赋值为外部存储器的目录
            String dirName = Environment.getExternalStorageDirectory()+"/MyDownload/";
            File f = new File(dirName);
            if(!f.exists()){
                f.mkdir();
            }
            File downloadFile=new File(f,"new.jpg");
            return downloadFile;
        }
        else{
            LogUtils.e("NO SD Card!");
            return null;

        }

    }

    public UpdateUI updateUI;


    public interface UpdateUI{
        void UpdateProgressBar(Integer values);
    }

    public void setUpdateUIInterface(UpdateUI updateUI){
        this.updateUI=updateUI;
    }
}

 

简单说明一下代码,在onPreExecute方法中,可以做了一些准备工作,如显示进度圈,这里为了演示方便,进度圈在常态下就是显示的,同时,我们还锁定了CPU,防止下载中断,而在doInBackground方法中,通过HttpURLConnection对象去下载图片,然后再通过int fileLength =connection.getContentLength();代码获取整个下载图片的大小并使用publishProgress((int) (total * 100 / fileLength));更新进度,进而调用onProgressUpdate方法更新进度条。*后在onPostExecute方法中释放CPU锁,并通知是否下载成功。接着看看Activity的实现:
activity_download.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:customView="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.zejian.handlerlooper.util.LoadProgressBarWithNum
        android:id="@+id/progressbar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        customView:progress_radius="100dp"
        android:layout_centerInParent="true"
        customView:progress_strokeWidth="40dp"
        customView:progress_text_size="35sp"
        customView:progress_text_visibility="visible"
        customView:progress_value="0"
        />

    <Button
        android:id="@+id/downloadBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="start download"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/progressbar"
        android:layout_marginTop="40dp"
        />
</RelativeLayout>

 

AsynTaskActivity.java

package com.zejian.handlerlooper;

import android.Manifest;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.view.View;
import android.widget.Button;

import com.zejian.handlerlooper.util.LoadProgressBarWithNum;
import com.zejian.handlerlooper.util.LogUtils;

/**
 * Created by zejian
 * Time 16/9/4.
 * Description:AsynTaskActivity
 */
public class AsynTaskActivity extends Activity implements DownLoadAsyncTask.UpdateUI {
    private static int WRITE_EXTERNAL_STORAGE_REQUEST_CODE=0x11;
    private static String DOWNLOAD_FILE_JPG_URL="http://img2.3lian.com/2014/f6/173/d/51.jpg";
    private LoadProgressBarWithNum progressBar;

    private Button downloadBtn;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_download);
        progressBar= (LoadProgressBarWithNum) findViewById(R.id.progressbar);
        downloadBtn= (Button) findViewById(R.id.downloadBtn);
        //create DownLoadAsyncTask
        final DownLoadAsyncTask  downLoadAsyncTask= new DownLoadAsyncTask(AsynTaskActivity.this);
        //set Interface
        downLoadAsyncTask.setUpdateUIInterface(this);
        //start download
        downloadBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //execute
                downLoadAsyncTask.execute(DOWNLOAD_FILE_JPG_URL);
            }
        });

        //android 6.0 权限申请
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
                != PackageManager.PERMISSION_GRANTED) {
            //android 6.0 API 必须申请WRITE_EXTERNAL_STORAGE权限
            ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
                    WRITE_EXTERNAL_STORAGE_REQUEST_CODE);
        }
    }


    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        doNext(requestCode,grantResults);
    }

    private void doNext(int requestCode, int[] grantResults) {
        if (requestCode == WRITE_EXTERNAL_STORAGE_REQUEST_CODE) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                // Permission Granted
                LogUtils.e("Permission Granted");
            } else {
                // Permission Denied
                LogUtils.e("Permission Denied");
            }
        }
    }

    /**
     * update progressBar
     * @param values
     */
    @Override
    public void UpdateProgressBar(Integer values) {
        progressBar.setmProgress(values);;
    }
}

 

在AsynTaskActivity中实现了更新UI的接口DownLoadAsyncTask.UpdateUI,用于更新主线程的progressBar的进度,由于使用的测试版本是android6.0,涉及到外部SD卡读取权限的申请,所以在代码中对SD卡权限进行了特殊处理(这点不深究,不明白可以google一下),LoadProgressBarWithNum是一个自定义的进度条控件。ok~,*后看看我们的运行结果:
%title插图%num
效果符合预期,通过这个案例,相信我们对AsyncTask的使用已相当清晰了。基本使用到此,然后再来聊聊AsyncTask在不同android版本中的差异。

二、AsyncTask在不同android版本的下的差异

这里我们主要区分一下android3.0前后版本的差异,在android 3.0之前,AsyncTask处理任务时默认采用的是线程池里并行处理任务的方式,而在android 3.0之后 ,为了避免AsyncTask处理任务时所带来的并发错误,AsyncTask则采用了单线程串行执行任务。但是这并不意味着android 3.0之后只能执行串行任务,我们仍然可以采用AsyncTask的executeOnExecutor方法来并行执行任务。接下来,编写一个案例,分别在android 2.3.3 和 android 6.0上执行,然后打印输出日志。代码如下:

package com.zejian.handlerlooper;

import android.app.Activity;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.zejian.handlerlooper.util.LogUtils;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by zejian
 * Time 16/9/5.
 * Description:
 */
public class ActivityAsyncTaskDiff extends Activity {
    private Button btn;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_async_diff);
        btn= (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new AysnTaskDiff("AysnTaskDiff-1").execute("");
                new AysnTaskDiff("AysnTaskDiff-2").execute("");
                new AysnTaskDiff("AysnTaskDiff-3").execute("");
                new AysnTaskDiff("AysnTaskDiff-4").execute("");
                new AysnTaskDiff("AysnTaskDiff-5").execute("");
            }
        });
    }

    private static class AysnTaskDiff extends AsyncTask<String ,Integer ,String>{
        private String name;
        public AysnTaskDiff(String name){
            super();
            this.name=name;
        }

        @Override
        protected String doInBackground(String... params) {
            try {
                Thread.sleep(2000);
            }catch (Exception ex){
                ex.printStackTrace();
            }

            return name;
        }

        @Override
        protected void onPostExecute(String s) {
            super.onPostExecute(s);
            SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            LogUtils.e(s+" execute 执行完成时间:"+df.format(new Date()));
        }
    }

}

 

案例代码比较简单,不过多分析,我们直接看在android 2.3.3 和 android 6.0上执行的结果,其中android 2.3.3上执行Log打印如下:
%title插图%num
在 android 6.0上执行Log打印如下:
%title插图%num
从打印log可以看出AsyncTask在android 2.3.3上确实是并行执行任务的,而在 android 6.0上则是串行执行任务。那么了解这点有什么用呢?其实以前我也只是知道这回事而已,不过*近在SDK开发中遇到了AsyncTask的开发问题,产生问题的场景是这样的,我们团队在SDK中使用了AsyncTask作为网络请求类,因为现在大部分系统都是在Android 3.0以上的系统运行的,所以默认就是串行运行,一开始SDK在海外版往外提供也没有出现什么问题,直到后面我们提供国内一个publisher海外版本时,问题就出现了,该publisher接入我们的SDK后,他们的应用网络加载速度变得十分慢,后来他们一直没排查出啥问题,我们这边也在懵逼中……直到我们双方都找到一个点,那就是publisher的应用和我们的SDK使用的都是AsyncTask作为网络请求,那么问题就来,我们SDK是在在Application启动时触发网络的,而他们的应用也是启动Activity时去访问网络,所以SDK比应用先加载网络数据,但是!!!AsyncTask默认是串行执行的,所以!!只有等我们的SDK网络加载完成后,他们应用才开始加载网络数据,这就造成应用的网络加载延迟十分严重了。后面我们SDK在内部把AsyncTask改为并行任务后问题也就解决了(当然这也是SDK的一个BUG,考虑欠佳)。在Android 3.0之后我们可以通过下面代码让AsyncTask执行并行任务,其AsyncTask.THREAD_POOL_EXECUTOR为AsyncTask的内部线程池。

new AysnTaskDiff("AysnTaskDiff-5").executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,"");
  • 1

*个参数传递是线程池,一般使用AsyncTask内部提供的线程池即可(也可以自己创建),第二个参数,就是*终会传递给doInBackground方法的可变参数,这里不传,所以直接给了空白符。执行效果就不再演示了,大家可以自行测试一下。
ok~,到此AsyncTask在不同android版本中的差异也分析完,感觉文章有点长了,那么AsyncTask工作原理分析就放到下篇吧。

关于Virtual Private Server

我现在放在美国的,访问大陆意外很快,但是访问大陆很慢。大家觉得放在香港会不会改善?
访问 大陆 virtual15 条回复 • 1970-01-01 08:00:00 +08:00
andyliu 1
andyliu 2012-06-08 14:57:54 +08:00
会 hk看ping值不错,但是打开速度一般。

带宽很一般
Zhang 2
Zhang 2012-06-08 14:58:31 +08:00
@andyliu 我要疯了
qiuai 3
qiuai 2012-06-08 15:03:26 +08:00
一般来说,香港的带宽是足够的.只要你不是用来做什么特殊的服务..联系我吧.Q:39831817
shiny 4
shiny 2012-06-08 15:04:42 +08:00
“访问大陆意外很快,但是访问大陆很慢”
看上去这句话自相矛盾么,看不懂。
Zhang 5
Zhang 2012-06-08 15:07:31 +08:00
@shiny 访问大陆以外的网站很快,访问大陆以内的网站很慢
finler 6
finler 2012-06-08 15:46:19 +08:00
我猜lz其实是用来做*的。
考虑用这个 code.google.com/p/chnroutes/
tension 7
tension 2012-06-08 15:54:55 +08:00
放国内吧
Zhang 8
Zhang 2012-06-08 15:57:53 +08:00
@finler 我说错了,是virtual private network
Zhang 9
Zhang 2012-06-08 15:58:17 +08:00
所以怎么可能放在国内呢?
welsmann 10
welsmann 2012-06-08 16:06:07 +08:00
是敏感词的原因么。。。为什么要写这么长的全名呢。。。。一眼愣是没看明白是啥。。
Zhang 11
Zhang 2012-06-08 16:07:53 +08:00
@welsmann 害怕是
Zhang 12
Zhang 2012-06-08 16:22:04 +08:00
@finler 貌似不支持L2TP呀
Zhang 13
Zhang 2012-06-08 16:22:40 +08:00
PPTP容易受到当局干扰,我已经受到过了
finler 14
finler 2012-06-09 09:42:57 +08:00
@Zhang 有人说可以l2tp,还得自己试试。pptp穿越性能一般,现在很多地方收紧了,即使拨上去也无法翻。
不知道pptp和l2能够共存。
Zhang 15
Zhang 2012-06-09 09:58:18 +08:00
@finler 目前pptp还能穿,不过打算义无反顾奔L2TP

ubuntu机器单网卡多IP问题

现有台ubuntu机器,单网卡上配置了多个IP,比如
192.168.10.51 – 59
怎么可以设定某个程序使用某个特定的IP地址发送/接受包?

当然不考虑改动程序代码的方法
网卡 Ubuntu IP22 条回复 • 1970-01-01 08:00:00 +08:00
liwei 1
liwei 2012-06-08 22:47:33 +08:00
如果程序通信的地址是固定,可以试试加一条路由:

ip route add DEST_ADDRESS via 192.168.10.51 dev eth0

PS: 不太清楚为什么会有这样的需求,能详细说说你需要这样配置的场景么?
deerlamp 2
deerlamp 2012-06-08 22:58:59 +08:00
@liwei 比如我从这台机器访问别的机器,想让对方看到我是在某个ip上的
liwei 3
liwei 2012-06-08 23:08:01 +08:00
@deerlamp 那你就只配置一个IP呗,干嘛配置那么多
deerlamp 4
deerlamp 2012-06-08 23:24:46 +08:00
@liwei …但是其他ip有其他的需要啊。。
kendisk 5
kendisk 2012-06-08 23:37:13 +08:00 ❤️ 1
开虚拟机,一个虚拟机可以给 128内存,装上linux,可以有很多了、
ljbha007 6
ljbha007 2012-06-08 23:42:29 +08:00
其实你可以把你的问题和要实现的功能说得更具体一点 我感觉你说的办法不一定是*好的办法 但是前提是得知道你具体的需求
deerlamp 7
deerlamp 2012-06-08 23:43:49 +08:00
@kendisk 这方法不错
deerlamp 8
deerlamp 2012-06-08 23:47:11 +08:00
@ljbha007 比如我在这台机器上开个客户端程序访问某服务器,但让对方服务器看到的是这个客户端程序是从某个特定的ip地址(这里比如是192.168.10.56,但该机器单网卡绑定了51-59个多个ip)访问的
服务器和该ubuntu机器是在同个lan里面
ljbha007 9
ljbha007 2012-06-08 23:51:48 +08:00
@deerlamp 既然是同一台机器发出来的请求 为什么要分别从不同的IP发送呢? 如果IP仅仅是一个区分功能用的参数的话那完全没必要这么做;比如如果是HTTP协议可以通过在请求头中加入额外的参数或者直接在请求方法的参数中加入特定的参数

所以还是不清楚为什么你要这么做
deerlamp 10
deerlamp 2012-06-09 00:24:08 +08:00
@ljbha007 可能从ip地址的安全性上来考虑 不想暴露主ip地址
ljbha007 11
ljbha007 2012-06-09 01:11:58 +08:00
@deerlamp 那直接用nginx做反向代理代理好了
deerlamp 12
deerlamp 2012-06-09 04:48:42 +08:00
@ljbha007 但如果都在一台机器上的话,反向代理也可以做到隐藏IP吗?
flyingnn 13
flyingnn 2012-06-09 09:59:48 +08:00
用iptable试试行不?
humiaozuzu 14
humiaozuzu 2012-06-09 10:16:25 +08:00
http://superuser.com/questions/241178/how-to-use-different-network-interfaces-for-different-processes
这里有linux下解决方案,不过我没有发现现成的程序,mac下倒是有很多。
ljbha007 15
ljbha007 2012-06-09 12:25:44 +08:00
@deerlamp 不好意思 我看错了 我把“主IP地址”看成了“主服务器IP地址”
ljbha007 16
ljbha007 2012-06-09 13:21:36 +08:00
@deerlamp 通过一个ifconfig 可以为一个网卡获取多个IP

http://linux.byexamples.com/archives/111/configure-multiple-ip-for-a-same-network-interface/

但是要指定程序使用哪个网络界面必须要程序有相关的参数设置才行 如果那个程序是写死的使用某一个固定界面或者系统默认路由的话 那就没有办法了 只能开虚拟机
deerlamp 17
deerlamp 2012-06-09 13:42:54 +08:00
@ljbha007 谢谢 权衡到现在 也是觉得虚拟机的方法更靠谱
搜了一下ubuntu下有KVM或XEN两种开源方案,网上都说KVM是未来方向,但据我了解KVM是全虚拟化,安全性更好,但就性能和稳定还不及XEN,v2ex上已有这方面的讨论吗?
另外对于KVM,ubuntu 10.04和12.04的内核对KVM的支持有多大的不同?
ljbha007 18
ljbha007 2012-06-09 15:43:11 +08:00
@deerlamp 不知道
kfc315 19
kfc315 2012-06-09 15:57:10 +08:00
传说中的 address minting 么 = =
我觉得 @liwei 说得靠谱。
haijd 20
haijd 2012-06-09 16:05:52 +08:00
这个问题的*佳办法是,看你那个客户端软件有没有绑定ip的功能。
deerlamp 21
deerlamp 2012-06-09 16:14:17 +08:00
@haijd 有的可以 有的不可以而且非开源。。。而且目的服务器有多个,IP也未知,不好设定路由表;所以目前我在考虑弄虚拟机
haijd 22
haijd 2012-06-09 17:48:30 +08:00
@deerlamp 这种情况基本上只能用虚拟机了

有人用过 SingleHop 的服务器么?

感觉怎样?
SingleHop 服务器 用过14 条回复 • 1970-01-01 08:00:00 +08:00
eric_q 1
eric_q 2012-05-24 12:50:37 +08:00
用过一个月的芝加哥的,ping值300左右,走nlayer线路
Livid 2
Livid V2EX Moderator 2012-05-25 06:33:48 +08:00
起步价格比 Linode 高,但是貌似*低一级的套餐都提供了 3T 的流量。
rhwood 3
rhwood 2012-05-25 09:11:12 +08:00
@Livid 大流量不靠谱的多了去了,SingleHop?Stay away
coagent 4
coagent 2012-05-25 09:16:17 +08:00
稳定优先,流量不是越大越好。
Livid 5
Livid V2EX Moderator 2012-05-25 09:20:12 +08:00
@coagent
@rhwood 你们觉得比较稳定的 US hosting 有?
coagent 6
coagent 2012-05-25 11:12:33 +08:00
@Livid
US: MT、Linode、Rackspace 的都比较稳定
tension 7
tension 2012-05-25 11:14:19 +08:00
@Livid 回国吧!!
HiVPS 8
HiVPS 2012-05-25 11:57:39 +08:00
@Livid 有一个不错的机房,怎么PM你?
muxi 9
muxi 2012-05-25 13:18:16 +08:00
@Livid *近你又是搭建云产品,又是看各个主机商,你不是想在美国搞个类Linode来服务国内的P民吧?
如果是的话,我愿意成为首批付费/测试用户
Showfom 10
Showfom 2012-05-25 13:32:27 +08:00 ❤️ 6
@Livid 作为资深的国外数据中心专家(自称的呵呵),和欧美亚洲各国大大小小十多个机房打过交道,我来说说我心目中美国机房的排名

在不考虑国内访问速度的情况下:

1、Softlayer

2、LiquidWeb

3、Gigenet

4、Peer1

5、Layered Tech

6、Midphase

7、SingleHop

欧洲的:

1、LeaseWeb

2、OVH

不要去使用的美国机房:

1、Ubiquityservers

2、HE.net

有大量国人的机房(看自己选择了,这些机房还是有重视国内客户的,毕竟是帮他们赚钱的大头)

1、Krypt

2、QuadraNet

3、Psychz

4、FDCServers

对电信线路好的机房:

1、Peer1

2、He.net

3、QuadraNet

4、Krypt

5、Psychz

对网通好的机房:

对电信网通都好的机房:


Showfom 11
Showfom 2012-05-25 13:39:58 +08:00 ❤️ 4
美国的机房内幕太多,我还在做这行也不能明了说

简单的说吧,大头的资源都是在大型的网络服务商(ISP)或者大型的做实业起家的公司(大多是房地产的,对于机房有天然的优势)

然后他们自己建立机房(是真正的机房,基于地皮的建立),建立好以后,从带宽到房间到机柜成批的批发给各个小型的服务器提供商

在上述所说的服务器提供商里,真正有自己实力建立一整个机房的有:

1、Softlayer

2、 He.net

3、Peer1

4、LeaseWeb (他们把整个荷兰电信的机房给租用了)

5、OVH

6、LiquidWeb

其他的小型服务器提供商大多是租用几个机柜,或者一整排机柜,或者一个单独的房间放满机柜,然后自己拉线路自己申请IP,直接是看不出来他们和上述大型服务商的区别的

很多美国的机房都提供这种服务,比较出名的有CoreSite、He、Cogent、Colo4、ColoAt,等等等等
TONYHEAD 12
TONYHEAD 2012-06-15 13:01:16 +08:00
@Showfom 请问下 ThePlanet.com Internet Services, Inc. 属于哪一个范围的呢?谢谢。
Showfom 13
Showfom 2012-06-17 01:25:22 +08:00 ❤️ 1
@TONYHEAD 被 Softlayer 买去了,都属于 Softlayer 的网络
csx163 14
csx163 2012-06-17 02:36:08 +08:00
兽兽?给力啊

关于网络地址转换

我*近终于搞清楚路由器的作用了,原来路由器是用来连接两层网络的。我发现给某个远程服务器发包时,途中经过n个路由器,也就是说途中要经过n-1层网络,每穿越一层网络,ip包的地址都要改写一次,这样怪不得网络延时会很高了。要是ipv6普及后,我们是不是可以不需要这么多层网络,所有的网络设备都在一层网络上,这样数据包就不需要穿越n层网络了,这样就节省了ip包地址改写的时间,那时候网络的延时就会下降到很可观,对不对?真是好憧憬呀!大家积*畅想呀!
网络 路由器 IP12 条回复 • 1970-01-01 08:00:00 +08:00
Zhang 1
Zhang 2012-06-18 14:59:27 +08:00
个人感觉多层网络简直就是灾难呀?这种网络根本不是平的!
lululau 2
lululau 2012-06-18 15:01:22 +08:00
路由器主要功能是还路由吧。。。。
dndx 3
dndx 2012-06-18 15:11:43 +08:00
LZ你的理解是不正确的,如果你没有公网IP,边界路由器需要做SNAT才能保证你的数据包能正确回传。一旦数据包的IP被改写为公网IP后,在互联网的传输过程中,中继路由器不会再改动你的源IP而只负责接力传输。如果真的像你说的那样,那么互联网就根本不可能实现,因为同时传输的数据量大到无法估计,如果每一跳都做SNAT,那么没有任何一个路由器能存储这么多的链接信息。
iyten 4
iyten 2012-06-18 15:24:22 +08:00
我就觉得奇怪。难道说ttl没减1那么ip就被改动一次?这个是不可能的。在公网上传输ip 要nat的估计就是isp的安全措施了。比如说电信骨干网上不可能大量出现铁通的ip,如果有那就说明有问题了,那么这时候就会nat,还有企业内部出去的话也是要做nat的。欢迎下面继续喷。
013231 5
013231 2012-06-18 15:25:56 +08:00
樓主的理解是完全錯誤的.
1. IP報文的源IP和目標IP在傳輸時通常不會變化(也有例外, 比如NAT).
2. 路由節點間不是”分層”關係.
3. IPv6對減少路由節點的數量沒有明顯幫助.
clowwindy 6
clowwindy 2012-06-18 15:39:27 +08:00
楼主的理解适用于长城宽带和宽带通 XD
blank_dlh 7
blank_dlh 2012-06-18 15:56:00 +08:00
不是每层都NAT的亲。。
局域网和公网(相对来说)连接处的路由做NAT,公网上的路由节点只是做转发用的。
ichigo 8
ichigo 2012-06-18 16:28:32 +08:00
其实你还是没明白路由功能..
Zhang 9
Zhang 2012-06-18 17:42:45 +08:00
@ichigo 你明白吗?
Zhang 10
Zhang 2012-06-18 17:49:42 +08:00
每经过一次NAT,应该说目的地ip不会变,而来源地ip会变的
billryan 11
billryan 2012-06-18 18:21:03 +08:00
IPV6的出现主要是为了解决IPV4地址不够的问题,NAT正是由于IPV4地址不够才出现的临时性解决方案,但是NAT违背了网络设计的初衷(分层结构设计)。IPV6普及后NAT后慢慢消失,确实节约了地址映射的那部分时间,但这与路由器没有直接的关系,路由器主要的功能是根据目的地址进行路由选路(是工作在第三层(网络层)的网络设备)。NAT只适用于那些无法分配到足够公网IP地址的场合,而且这是由一个叫做「NAT盒」来干的事,与路由器没半毛钱关系… 建议楼主看看潘爱民翻译的《计算机网络》补一补网络方面的知识
dndx 12
dndx 2012-06-18 18:32:24 +08:00
@Zhang 正如 @billryan 所说,SNAT是为了解决IP地址不足的问题,比如你有一个IP地址192.168.1.2,你要请求8.8.8.8一些信息,如果没有路由器帮你做SNAT,你的数据包【有可能】被8.8.8.8收到,【但是】8.8.8.8不知道怎么将包回复给192.168.1.2,这种目标地址是私有地址的数据包在Internet上是无意义的,会直接被BGP路由器丢弃。

同 @ichigo ,建议LZ补一补基础知识…

关于路由分析

我从重庆联通发到美国的ip数据包离开重庆后就到达广西南宁,再到达广东。这时候我以为我的数据包要出境了,不过蹊跷的是我的数据包被发到北京后才出境。光是从广东到北京就花了300ms。我的神呀!大家帮忙分析分析。
数据包 发到 出境6 条回复 • 1970-01-01 08:00:00 +08:00
hu437 1
hu437 2012-06-18 22:44:00 +08:00
这没啥好分析的,你的电信运营商的路由问题,我们决定不了,分析了也没用呀
Zhang 2
Zhang 2012-06-18 22:52:30 +08:00
为什么不能直接从广东出境呀?
derekwei 3
derekwei 2012-06-18 22:54:43 +08:00
@Zhang 这个是ISP路由设置的问题,你是对此是无能为力的。又一次我空间的服务商路由设置错误,线路去欧洲饶了一圈又回到了美国。。。。。。。。。。。。。。
Zhang 4
Zhang 2012-06-18 22:56:49 +08:00
不晓得随着时间的推移,能不能尽量抄近道!
hu437 5
hu437 2012-06-19 13:42:05 +08:00
能不能抄近道要看ISP的路由设置了,比如现在有ABCDEF几个节点,
a->b->e->f这是一条线
a->d->e->f这是一条线
a->b->c->e->f这也是一条线
a->f这也是一条线

明显3*长,4*短,这就是路由规则,但是这个规则不是我们可以定义的,这些是由ISP来定义的,如果某一天ISP做了优化,就可能将3调到4
johnnie502 6
johnnie502 2012-06-27 16:22:47 +08:00
在ISP那边怎么走多数情况是由经济成本决定的,而不是效率….

为什么都开始搞研发效能?

研发效能是目前互联网企业和传统软件企业都高度关注的领域,一线互联网企业希望通过“研发效能”实现持续的研发能力提升以应对日趋复杂的产品开发;腰部厂商则希望通过“研发效能”实现弯道超车,充分发挥后来者居上的优势;更多中小企业看到国内一线互联网企业不约而同地在这个领域重点投入,纷纷也是摩拳擦掌准备在效能领域发力。

一夜之间,似乎只有推进了研发效能,才能提升研发团队的效率,才能让自己在和友商的比拼中不至于输在起跑线上。

那么现在企业的研发效能实践到底为企业带来了多大的优势,又帮企业解决了哪些问题呢?那些推行研发效能的企业现在的状态怎么样?研效问题到底解决了吗?

别急,这些问题其实大多都还没有解决,而且有些问题可能还变得更糟糕了。毕竟研发效能的实施没有捷径,需要摸着石头过河,肯定不会能像电影里面演得那样注定会有皆大欢喜的结局。经历了风雨,不一定能看见彩虹,更有可能会得重感冒。所以想要快速解决研发效能的问题,我们首先需要对研发效能有一个全局的认识,需要先从正向的角度来理解研发效能。

到底什么是研发效能

和敏捷的概念类似,到底什么是研发效能很难精确定义。其实很多复杂概念也不是定义出来的,而是逐步演化出来的,是先有现象再找到合适的表述。其实,效率和效能也从来都不是软件工程的专有名词,纵观人类发展史,就是生产力和生产效率不断提升的发展篇章,到了数字化时代,软件研发效能的重要性被凸显了出来。如果要用一句话来总结研发效能的话,我们会用“更高效、更高质量、更可靠、可持续地交付更优的业务价值”来总结。

%title插图%num图1:研发效能的“*性原理”

解释一下其中几个关键概念:

  • 更高效:价值的流动过程必须高效顺畅,阻力越小越好。
  • 更高质量:如果质量不行,流动越快,死的也会越快。
  • 更可靠:安全性和合规性要保障好。
  • 可持续:输出不能时断时续,小步快跑才是正道,不要憋大招。
  • 更优的业务价值:这是从需求层面来说的,你的交付物是不是真正解决了用户的本质问题。

在这个概念的引导下,我们引出持续开发,持续集成,持续测试,持续交付和持续运维的理念,它们是研发效能落地的必要实践。与此同时,我们还需要从流动速度,长期质量,客户价值以及数据驱动四个维度来对研发效能进行有效的度量。

为什么一线企业都开始搞研发效能

近几年,各大行业龙头企业都纷纷开始在研发效能领域发力,我们认为背后的原因有以下这么三点:

%title插图%num图2:组织层面的“谷仓困局”

01 很多企业存在大量重复造轮子

就像“中台“概念一样,现在很多大企业的产品线非常广,其中存在大量重复的轮子,如果我们关注业务上的重复轮子,那么就是业务中台;如果我们关注数据建设上的重复轮子,那么就是数据中台;如果我们关注研发效能建设上的重复轮子,那就是研效平台,其实研效平台在某种程度上也可以称之为”研发效能中台“,其目标是实现企业级跨产品跨项目的研发能力复用,避免原来每条产品线都在做研发效能所必须的”0 到 1“,没人有精力去关注更有价值的”1 到 n“。现代化的研效平台会统一来打造组织级别通用研发能力的*佳实践平台。

02 toC 产品已经趋向饱和

从商业视角来看,现在 toC 产品已经趋向饱和,过去大量闲置时间等待被 APP 填满的红利时代已经一去不复返了,以前业务发展*快,那么用烧钱的方式(粗放式研发,人海战术)换取更快的市场占有率达到赢家通吃是*佳选择,那个时代关心的是软件产品输出,研发的效率都可以用钱填上。而现在 toC 已经逐渐走向红海,同时研发的规模也比以往任何时候都要大,是时候要勒紧裤腰带过日子了,当开源(开源节流中的开源)遇到瓶颈了,节流就应该发挥作用。这个节流就是研发效能的提升,同样的资源,同样的时间来获得更多的产出。

03 部分企业存在“谷仓困局”

从组织架构层面看,很多企业都存在“谷仓困局”(图 2),研发各个环节内部可能已经做了优化,但是跨环节的协作可能就会有大量的流转与沟通成本,从而影响全局效率。基于流程优化,打破各个环节看不见的墙,去除不必要的等待,提升价值流动速度正是研发效能在流程优化层面试图解决的一大类问题。

研发效能真的能够提高吗

既然如此重要,那接下来的问题是研发效能是否真的能提高?

很不幸,我们的观点比较悲观。我们认为研发效能的*对值随着以下因素的增长必然会变得越来越差,就像我(声明一下,这里没有张乐老师)的头发一样,随着时间的推移必然会越来越少一样。

  • 软件架构本身的复杂度提升(微服务,服务网格等)
  • 软件规模的不断增长(集群规模,数据规模等)
  • 研发团队人员规模不断扩大引发沟通协作难度增长

所以,我们能做的不是提升研发效能的*对值,而是尽可能减缓研发效能恶化的程度,使其下降的不至于太快,努力保持现状就是成功。

%title插图%num图3:研发效能的鸿沟

减缓研发效能恶化我们能干啥

看清了本质后,关于如何减缓研发效能的恶化,我们能做点什么呢?

可以说研发效能的涉猎面是很广的,软件研发的每个阶段都有研发效能需要关注的问题,腾讯提出的“研发效能双流模型”可以说很好的诠释了这一概念。双流模型从软件研发的各个阶段提出了研发效能提升的各种工程实践,并且倡导需求价值流和研发工程流的自动联动。

%title插图%num图4 研发效能的双流模型

这里我们列举一些实践给大家抛砖引玉一下,下期的文章我会更系统地来说明其中的*佳实践。

  • 可以通过 All-in-one 的开发环境降低每位开发人员开发环境准备的时间成本,同时又能保证开发环境的一致性。更高级的玩法是使用云端集成开发环境 IDE,实现只要有浏览器就能改代码,这一领域国内典型的代表就是腾讯云 CODING 旗下的 Cloud Studio 以及 Github 目前处于 beta 测试阶段的 CodeSpaces。
  • 可以借助基于 AI 的代码提示插件,大幅度提升 IDE 中代码的开发效率。输入一段相同的代码,不借助 AI 代码提示插件,需要敲击键盘 200 次,启用插件可能只需要 50 次键盘敲击,这样可以更容易让开发工程师进入“心流“状态,实现”人码合一“。
  • 代码的静态检查没有必要等到代码递交后由 CI 中的 Sonar 流程来发起,那个时候发现问题再修复为时已晚,完全可以通过 Sonar Lint 插件结合 IDE 实时发起本地的代码检查,有问题直接在 IDE 中提示,直接修复,这样开发工程师会更愿意修复问题,因为成本更低,也不会引起修复后的再次发版。
  • 单元测试比较耗费时间,可以借助 EvoSuite 之类的工具降低单元测试的开发工作量。
  • 对于规模较大的项目,每次修改后编译时间比较长。可以采用增量编译,甚至是分布式编译(Distcc 和 CCache)提升效率,对于 Maven 项目还可以通过缓存 pom 依赖树进一步降低编译时间。
  • 前端开发可以借助 JRebel 和 Nodemon 之类的工具使前端开发预览的体验更流畅,实现前端代码的“所见即所得”,避免重复的编译、打包、部署和重启步骤,以此提高开发过程的流畅度。
  • 选择适合项目的代码分支策略对提升效率也大有帮助。
  • 构建高度自动化的 CI 和 CD 流水线将大幅提升价值的流转速率。
  • 选择合适的发布策略也会对效能和风险之间的平衡起到积*的作用。比如架构相对简单,但是集群规模庞大,优选金丝雀,如果架构比较复杂,但是集群规模不是太大,可能蓝绿发布更占优势。
  • 引入 DevSecOps 与 DevPerfOps 实践,使安全和性能不再局限在测试领域,而是形成体系化的全局工程能力,让安全测试成为安全工程,性能测试成为性能工程。

研发效能的“罗生门”

好了,理解了研发效能的正面观点后,我们再回来看看研发效能在实际落地过程中又是一番什么样的景象。

可以说理想很丰满,但是现实很骨感,下面就我一起看看国内研发效能的各种乱象。

01 迷信单点局部能力,忽略全局优化和拉通的重要性

研发效能的单点能力其实都不缺,各个领域都有很多不错的垂直能力工具,但是把各个单点能力横向集成与拉通,能够从一站式全流程的维度设计和规划的研发效能成熟平台还是凤毛麟角。现在国内很多在研效领域有投入的公司很多其实还在建设,甚至是重复建设单点能力的研效工具,这个思路在初期可行,但是单点改进的效果会随着时间收益递减,企业往往缺少从更高视角对研发效能进行整体规划的能力。很多时候局部优化并不能带来全局优化,有时候还会是全局恶化。

02 具有普适性的通用研发效能工具其实没有专属工具来的好用

既然打造了研发工具,那就需要到业务部门进行推广,让这些研效工具能够被业务部门使用起来。其实,很多比较大的业务团队在 CI/CD、测试与运维领域都有自己的人力投入,也开发和维护了不少能够切实满足当下业务的研发工具体系。此时要把新打造的研效工具来替换业务部门原来的工具,肯定会遇到很强的阻力。除非新的工具能够比老工具好 10 倍,用户才可能有意愿替换,但实际情况是新打造的工具为了考虑普适性很有可能还没有原来的工具好,再加上工具替换的学习成本,所以除非是管理层强压,否则推广成功的概率微乎其微。即使是管理层强压,实际的执行也会大打折扣,接入但不实际使用的情况不在少数。

03 用“伪”工程实践和“面子工程”来滥竽充数

如果你去比较国内外研发效能工程实践的差距,你会发现国内公司和硅谷公司的差距还是相当明显的。但是当你逐项(比如单元测试,静态代码扫描,编译加速等)比较双方开展的具体工程实践时,你会惊讶地发现从实践条目的数量来说,国内公司的一点都不亚于硅谷公司,在某些领域甚至有过之而不及。那为什么这个差距还会如此明显呢?我们认为这其中*关键的点在于,国内的很多工程实践是为了做而做,而不是从本质上认可这一工程实践的实际价值。这里比较典型的例子就是代码评审和单元测试。虽然很多国内一线互联网企业都在推进代码评审和单元测试的落地,但是在实际过程中往往都走偏了。代码评审变成了一个流程,而实际的评审质量和效果无人问津,评审人的评审也不算工作量,也不担任何责任,这样的代码评审能有什么效果,结果可想而知。单元测试也沦为一种口号,都说要贯彻单测,但是在计划排期的时候压根没有给单测留任何的时间和人力资源,可想而知这样的单测是否能成功开展。所以,国内公司缺的不是工程实践的多少,而是工程实践执行的深度。不要用“伪”工程实践和“面子工程”来滥竽充数。

04 忽略研发效能工具体系的长尾效应

再回到研效工具建设的话题上,很多时候管理团队希望能够打造一套一站式普遍适用的研发效能平台,希望公司内大部分业务都能顺利接入,这和想法的确非常好,但是不可否认的,研效平台和工具往往具有非标准的长尾效应,我们很难打造一套统一的研效解决方案来应对所有的业务研发需求,各种业务研发流程的特殊性是不容忽视的。退一万步说,即使我们通过高度可配置化的流程引擎实现了统一研效解决方案,那么这样的系统会因为过于灵活,使用路径过多而易用性变得很差。这两者的矛盾是很难调和的。

05 盲目跟风

再来看看一些中小型研发团队,他们看到国内一线互联网企业在研效领域不约而同的重兵投入,所以也会跟风。他们往往试图通过引进先进企业的工具和人才来作为研效的突破口,但实际的效果可能差强人意。一线互联网企业的研效工具体系固然有其先进性,但是是否能够适配你的研发规模和流程是有待商榷的。很多时候研效工具应该被视为起点,而不是终点,就像你买了一辆跑车,你依旧不能成为赛车手。

06 迷信外部专家

引入一线互联网专家其实也是类似的逻辑,我常常会被问及这样的问题:“你之前主导的研效提升项目都获得了成功,如果请你过来,多久能搞定”?这其实是一个无解的问题。一定程度上,投入大,周期就会短,但是,实施周期不会因为投入无限大而无限变短。我可以帮你避开很多曾经踩过的坑,尽量少走弯路,犯过的错误不再次犯,但是,适合自己的路子还是要靠自己走出来,拔苗助长只会损害长期利益。

07 研效度量的罪与罚

*后再来看看度量。研发效能的度量一直以来都是很敏感的话题。科学管理时代我们奉行“没有度量就没有改进”,但是数字时代这一命题是否依然成立需要我们的反思。现实事物复杂而多面,度量正是为描述和对比这些具象事实而采取的抽象和量化措施,从某种意义上来说,度量的结果一定是片面的,反映部分事实。但没有银弹,也没有完美的效能度量。数据本身不会骗人,但数据的呈现和解读却有很大的空间值得探索。那些不懂数据的人是糟糕的,而**糟糕的人是那些只看数字的人。当把度量变成一个指标游戏的时候,永远不要低估人们在追求指标方面“创造性”,总之我们不应该纯粹面向指标去开展工作,而应该看到指标背后更大的目标,或者是制定这些指标背后的真正动机。

总体来看,对于研发效能,我认为*重要的不是技术升级,而应该是思维升级,我们身处数字化的变革之中,需要转换的是自己的思维方式,我们需要将科学管理时代的思维彻底转为数据经济时代的思维。

研发效能的“冷思考”

*后,回到工程师层面,研发效能的提升对我们而言又意味着什么?

01 工具效率的提升并没有减少我们的工作时长

新工具新平台在帮助我们提升效率的同时,也不断增加着我们学习的成本。用前端开发来举例子,以全家桶为基础的前端工程化大幅度提高了前端开发的效率,但与此同时前端开发工程师的学习成本却在成倍增加,“又更新了,实在学不动了”一定程度反映了前端同开发的悲哀和无奈。

02 技术的升级正在不断模糊工作和生活的边界

早年时候的工作沟通除了面聊以外主要靠邮件,非工作时段老板给你发邮件你有各种正当理由不用及时回复,可是现在及时通讯工具 IM(那个消息已读提示,你懂的)再结合各种 ChatOps 实践,已经让工程师已经无法区分什么是工作什么是生活了,这难道是我们想要的吗?

随着在研发效能领域的不断投入,会有越来越多的研效工具诞生,所有这些工具都使人与工作之间的链接更加紧密,人越来越像工具,而工具越来越像人。我们之所以创造工具是想减轻我们自己的工作,但现实却很可能发展成,我们*终沦为被亲手创造的工具奴役。我们致力于的研发效能,究竟会成就我们,还是毁了我们?值得我们深入思考。

对于研发效能,实施的思路不对,方法不对会搞垮一个团队,我们需要的是体系化的方法论和相应的工程实践。

《中国的全面小康》白皮书新闻发布会答记者问

(2021年9月28日)   彭博新闻社记者:贫富差距现在有多大?国家会怎样控制、缩小贫富差距?税务方面的政策会不会起大的作用?   国家发展改革委党组成员、副主任兼国家统计局党组书记、局长宁吉喆:这是个很重要的问题。可以说,我国全面建成小康社会的进程,是贫困现象不断减少的过程,也是人民日益富裕起来的进程。党的十八大以来,我国经济实力持续跃升,人民生活水平全面提高,居民收入分配格局逐步改善。虽然存在贫富差距,但城乡、地区和不同群体居民收入差距总体上趋于缩小。   一是城乡之间居民收入差距持续缩小。随着国家脱贫攻坚和农业农村改革发展的深入推进,农村居民收入增速明显快于城镇居民,城乡居民相对收入差距持续缩小。从收入增长上看,2011—2020年,农村居民人均可支配收入年均名义增长10.6%,年均增速快于城镇居民1.8个百分点。从城乡居民收入比看,城乡居民人均可支配收入比逐年下降,从2010年的2.99下降到2020年的2.56,累计下降0.43。2020年,城乡居民人均可支配收入比与2019年相比下降0.08,是党的十八大以来下降*快的一年。   二是地区之间居民收入差距逐年下降。在区域协调发展战略和区域重大战略实施作用下,地区收入差距随地区发展差距缩小而缩小。2011—2020年,收入*高省份与*低省份间居民人均可支配收入相对差距逐年下降,收入比由2011年的4.62(上海与西藏居民收入之比)降低到2020年的3.55(上海与甘肃居民收入之比),是进入新世纪以来的*低水平。2020年,东部与西部、中部与西部、东北与西部地区的收入之比分别为1.62、1.07、1.11,分别比2013年下降0.08、0.03和0.18。   三是不同群体之间居民收入差距总体缩小。基尼系数是衡量居民收入差距的常用指标。基尼系数通常用居民收入来计算,也用消费支出来计算,世界银行对这两种指标都进行了计算。按居民收入计算,近十几年我国基尼系数总体呈波动下降态势。全国居民人均可支配收入基尼系数在2008年达到*高点0.491后,2009年至今呈现波动下降态势,2020年降至0.468,累计下降0.023。同时居民收入分配调节在加大。“十三五”时期,全国居民人均转移净收入年均增长10.1%,快于居民总体收入的增长。还要看到,在世界银行数据库中,2016年中国消费基尼系数为0.385,比当年收入基尼系数0.465低0.080,而消费的数据更直接地反映了居民实际生活水平。   四是基本公共服务均等化加快推进。看居民收入,不仅要看家庭可支配收入,还要看政府为改善民生所提供的公共服务。在全面建设小康社会进程中,各地区各部门积*推进基本公共服务均等化。完善多层次社会保障体系成效明显,目前我国已经建成世界上*大的社会保障网,基本医疗保险覆盖超13.5亿人,基本养老保险覆盖超10亿人。住房保障和供应体系建设稳步推进,全国已累计建设各类保障性住房和棚改安置住房8000多万套,帮助2亿多困难群众改善了住房条件。教育公平和质量不断提升,2020年九年义务教育巩固率为95.2%。基本医疗和公共卫生服务改善,2020年一般公共预算卫生健康支出1.92万亿元。人民群众通过自己劳动得到的收入、经营得到的收入、转移支付得到的收入在增加。同时,有一些收入并没有进入家庭,而是通过公共服务提供给广大群众,这方面在我们这样的中国特色社会主义国家,各部门各地区做的工作尤其多。   “十四五”时期,进一步控制和缩小贫富差距,既要做大蛋糕,又要分好蛋糕。要坚持发展是*要务,通过发展经济、辛勤劳动、扩大就业增加居民收入。同时,坚持按劳分配为主体、多种分配方式并存,提高劳动报酬在初次分配中的比重,健全工资合理增长机制,着力提高低收入群体的收入,扩大中等收入群体;完善按要素分配政策制度,增加中低收入群体的要素收入;完善再分配机制,加大税收、社保、转移支付等调节力度和精准性;发挥第三次分配的作用,发展慈善事业;构建初次分配、再分配、三次分配协调配套的基础性制度安排,促进社会公平正义,促进人的全面发展,使全体人民朝着共同富裕目标扎实迈进。   你刚才专门提到税收,税收在收入分配中已经发挥重要作用,今后无论是在初次分配,还是在再分配、三次分配当中,都要发挥好税收杠杆的作用。谢谢。   CNBC记者:中国的一线城市发展还存在哪些不足之处?比如在养老、社会保障等方面,一线城市未来的发展方向和重点是什么?   宁吉喆:改革开放以来,北京、上海、深圳、广州等一线城市及其他超大特大城市经济大幅增长、人口显著增加、开放不断扩大、社会事业蓬勃发展,已经成为中国经济增长的重要引擎、对外开放的重要枢纽和国家治理的重要支撑,也日益成为人民安居乐业、享受美好生活的重要空间载体。同时,这些城市也存在交通拥堵、房价高企、发展受限等世界各国普遍存在的“城市病”。这些都是发展中的问题、前进中的困难、成长中的烦恼。至于在养老社保方面,一线城市总体水平并不差,几个一线城市人均期望寿命都在80岁以上,名列各类城市前茅。需要改进的是控制成本、改善服务。“十四五”规划纲要对于完善城镇化空间布局,优化提升超大特大城市中心城区功能等进行了规划部署,有以下几点。   一是促进超大特大城市高质量、可持续发展。要统筹兼顾经济、生活、生态、安全等多元需要,推动超大特大城市转变开发建设方式,加强城市治理风险防控。坚持人民城市人民建、人民城市为人民,深入把握城市发展规律,统筹发展和安全,切实推动城市发展方式由规模扩张向内涵提升转变,更加注重民生,稳步提高社会保障水平,提升城市治理现代化水平,使城市更健康、更安全、更宜居。同时,进一步强化超大特大城市的中心辐射作用,不断优化经济发展空间格局、实现区域协调发展,更好带动乡村振兴、促进城乡融合发展。   二是合理降低超大特大城市开发强度和人口密度。要科学规划城市生产、生活、生态空间,有序疏解中心城区一般性制造业、区域性物流基地、专业市场等功能和设施以及过度集中的公共资源,加强城市治理中的风险防控。比如,北京将立足全国政治中心、文化中心、国际交往中心、科技创新中心功能定位,建设和谐、宜居、美丽的大国首都。同时,以非首都功能疏解为着力点推动京津冀协同发展向更高水平迈进,加强京津冀及周边地区空气质量监测和联防联控,发挥北京科技创新优势带动津冀传统行业改造升级,提升对周边地区的辐射带动作用。   三是优化提升超大特大城市核心竞争力。增强全球资源配置、科技创新策源、高端产业引领功能,率先形成以现代服务业为主体、先进制造业为支撑的产业结构,提升综合能级与国际竞争力。比如,上海将提升城市能级和核心竞争力,发挥在长三角地区的龙头带动作用,以强化全球资源配置、科技创新策源、高端产业引领、开放枢纽门户“四大功能”为引领,不断推动国际经济、金融、贸易、航运和科技创新“五个中心”能级跃升,为长三角高质量发展和参与国际竞争提供服务,引领长三角一体化发展。又如,深圳将以建设中国特色社会主义先行示范区为引领,建设综合性国家科学中心,加快推进前海、河套等粤港重大合作平台,在粤港澳大湾区规则衔接、机制对接方面先行先试,充分发挥深圳在大湾区建设中核心引擎作用,辐射带动周边地区加快发展。广州也将发挥在大湾区建设中的重要作用,包括完善广深港、广珠澳科技创新走廊等。谢谢。   日本电视网记者:在中国的少子化与老龄化加速增长的背景下,有消息指出,可能对中国今后的经济发展也会产生影响。请问在这种情况下,中国可以继续保持全面小康社会吗?   宁吉喆:进入新世纪以来,中国的人口总量和结构都发生了一些新的变化。2020年开展的第七次全国人口普查全面查清了我国人口的数量、结构、分布等方面情况,这个成果已经公布了。虽然我国人口总量增速有所放缓,总和生育率下降,老龄化程度加深,但总体上看,人口红利依然存在,人才红利优势后发,人口健康水平不断提升。随着人口政策的逐步完善,我国经济发展长期向好,在全面建成小康社会基础上全面建设社会主义现代化国家,仍然具备较好的人力资源保障。   一是劳动力资源依然丰富,人口红利继续存在。我国仍然是世界人口*大国,人口总量仍然保持增长,劳动年龄人口总量仍然庞大。我国16-59岁劳动年龄人口达到8.8亿人,还有3亿多育龄妇女,每年能保持1000多万的出生人口规模。2020年,我国农民工总量仍达2.86亿人。人口数量增长产生的红利仍然存在,劳动力资源仍然较为充沛,为经济持续发展提供了人口红利支撑。   二是人口素质明显提高,人才红利新的优势逐步显现。过去十年,我国人口受教育程度明显提升。2020年,我国16-59岁劳动年龄人口平均受教育年限达到10.75年,比2010年的9.67年提高了1.08年。教育事业过去十年大发展,其中,大专及以上受教育程度人口2.08亿人,占劳动年龄人口的比重达到23.61%,比2010年大幅提高了11.27个百分点。人才规模比重上升了超过10个百分点,翻了近一倍。这有利于促进我国经济发展方式加快转变、产业结构优化升级、全要素生产率不断提高,为经济高质量发展提供新的人才红利支撑。   三是居民健康水平大幅改善,劳动力资源条件优化。健康既是福祉,也是生产力。随着医疗卫生事业改革发展,我国人口身体素质明显提高。2019年,我国人口预期寿命达到77.3岁,比2010年提高2.47岁,这个提高幅度也是很大的。中老年人的身体状况总体改善,现在有许多人虽然已经达到了老年,但是身体素质还是相当好。2020年,我国婴儿死亡率和孕产妇死亡率也分别下降至5.4‰和16.9/10万,两端年龄人口的身体素质都改善了。我国居民的健康水平总体上还优于中高收入国家平均水平,卫生健康事业取得的成绩,也为经济持续发展提供有效的劳动力投入支持。同样是劳动力,身体健康程度改善了,有利于劳动力资源发挥作用。   四是少儿人口数量和比重上升,新一代劳动力资源正在成长。“单独二孩”“全面二孩”政策实施以来,我国出生人口数量明显回升。第七次全国人口普查数据显示,0-14岁少儿人口数量比2010年增加了3092万人,比重上升1.35个百分点。“二孩”生育率明显提升,出生人口中“二孩”占比由2013年的30%左右上升到2017年的50%左右。今年国家实施三孩生育政策及配套支持措施,有助于促进出生人口增加,改善人口年龄结构,实现人口长期均衡发展。谢谢。   中国日报记者:中国是世界第二大经济体,也是拉动世界经济发展的主要引擎之一,去年中国成为世界上唯一实现经济正增长的主要经济体。当前世界经济复苏的势头不是很稳定,在这样的背景下,中国全面建成小康社会对世界意味着什么?会为世界经济带来哪些新的发展机遇?   宁吉喆:中国全面建成小康社会,意味着全球人口*多的国家和世界上*大的发展中国家经济繁荣、民生改善,这本身就是对世界和平发展的巨大贡献。同时,中国全面建成小康社会也为世界经济复苏提供了动力、创造了机遇,为构建人类命运共同体贡献了中国智慧、中国力量。中国离不开世界,世界也离不开中国。   首先,中国全面建成小康社会,为全球减贫事业作出了突出贡献。改革开放以来,中国有7.7亿农村贫困人口摆脱了贫困,占同期全球减贫人口的70%以上,2020年又消除了*对贫困现象,提前10年实现了《联合国2030年可持续发展议程》中的减贫目标,14亿多中国人民踏上全面建设社会主义现代化国家的新征程,这是人类历史上前所未有的大变革、大事件。近年来,在世界贫困人口不减反增、全球减贫事业遭遇瓶颈的背景下,中国减贫取得的成就显著缩小了世界贫困人口的版图,为全球减贫事业注入了信心和力量。   第二,中国全面建成小康社会,为世界经济增长和复苏提供了拉动力量。从2006年起,我国已连续15年成为世界经济增长的*大贡献国,连续多年对世界经济增长的平均贡献率超过30%,成为世界经济增长的主要引擎。2020年,我国国内生产总值超过100万亿元,这是克服了新冠肺炎疫情的影响,成为全球唯一实现正增长的主要经济体。今年上半年,中国经济同比增长12.7%,不仅成为拉动全球经济和贸易复苏的重要力量,而且为维护全球供应体系的稳定发挥了积*作用。一年多来,中国已向世界供应口罩数千亿只、疫苗十几亿剂,有力支持了各国抗疫发展。   第三,中国全面建成小康社会,为世界经济繁荣发展带来了巨大机遇。目前,中国已经成为全球第二大消费市场、*货物贸易大国,利用外资和对外投资稳居世界前列。随着我们加快构建以国内大循环为主体、国内国际双循环相互促进的新发展格局,中国市场的潜力将日益迸发,中国开放的大门将越开越大,为世界各国提供更广阔的市场、更有力的合作契机和更宽广的发展空间。据测算,今后五年,中国从世界其他国家和地区进口货物将超过10万亿美元,向世界其他国家和地区直接投资将超过5500亿美元,这必将为全球经济稳定复苏和持续发展提供强大动力。   *后,中国全面建成小康社会,为发展中国家走向现代化拓展了新的途径。经过新中国70多年特别是改革开放40多年的建设和发展,中国经济面貌从一穷二白到总量全球第二,中国人民生活从温饱不足到全面小康,迎来了从站起来、富起来到强起来的伟大飞跃。在全面建成小康社会进程中,我们创造了经济快速发展和社会长期稳定两大奇迹,这不仅给中国人民带来实实在在的好处,也大大提升了人类社会的发展水平。2019年起,我国人均GDP超过1万美元,这使得世界上人均GDP超过1万美元的经济体人口总量又增加了14亿人,接近30亿人,翻了近一番,这无疑是人类社会发展的巨大福音。中国全面建成小康社会的生动实践,给世界上那些既想加快发展又希望保持自身独立性的国家和民族提供了全新选择。谢谢。

给Arm生态添把火,腾讯Kona JDK Arm架构优化实践

前言

Arm架构以其兼具性能与功耗的特点,在智能终端以及嵌入式领域得到了广泛的使用,不断扩大其影响力。而在PC端以及数据中心,之前往往是x86架构在其中发挥着主要的作用。*近,随着人工智能、云计算等技术的兴起,5G网络的不断成熟,万物互联的时代是的应用的需求越来越多样化,使得对于芯片架构的需求也越来越多样化。

Arm架构在提供可靠性能的基础上,低功耗、低开销的特点使得它被越来越广泛的应用到数据中心和云计算中,成为其中必不可缺少的重要组成部分。亚马逊投入大量精力自研Arm服务器,并应用到AWS服务中,*多实现了成本45%的降低;阿里巴巴也在云服务中大量采用Arm服务器,并积*参与Linaro,Adoptium等组织,不断推动Arm架构的发展。

*近几年,腾讯对于Arm架构的需求也不断增加,各个产品线也不断引入Arm服务器,对于Arm架构软件的需求也在不断增长。KonaJDK团队在腾讯公司内部提供高性能、高稳定性的商用JDK版本,坚定地将Arm架构作为KonaJDK重点支持的架构之一,不断扩展JDK在Arm架构的功能,并不断提高Arm架构中JDK的性能。

%title插图%num

随着Arm架构在终端和云计算场景的广泛应用,JDK需要做好对于Arm架构的支持工作,才能更好地得到发展。目前在JDK社区,Arm架构属于*梯队支持架构。而对于Arm架构而言,Java语言“一次编译,到处运行” 的特性适合业务应用无缝推广到Arm平台,而JDK则是Java应用运行的必要条件。JDK对于Arm架构的支持,也是Arm生态推广的有力支撑。在这个过程中,KonaJDK团队希望和Arm紧密合作,共同发展。

腾讯和Arm在JDK方面的合作交流

KonaJDK

目前腾讯和Arm在JDK方面已经有了深入的交流和合作。双方针对JDK在Arm架构常见的性能问题,对于Arm架构新特性的支持情况等方面进行了广泛和深入的讨论,通过性能测试、数据交流、技术研讨等形式不断推动JDK在Arm架构的发展。

KonaJDK团队Arm平台优化技术介绍

KonaJDK

目前在Arm架构,KonaJDK平台已经发布了JDK8和JDK11两个版本,在2021晚些时候还会发布*新的JDK17版本。Kona JDK团队从功能、性能多方面出发,在Arm架构支撑KonaJDK的通用特性,并针对架构特征进行优化,保证Java应用向Arm平台迁移的一致性,为Arm架构推广做好准备。

ZGC:

GC使得程序不再需要手动控制内存的释放,有效的降低了内存管理相关错误产生的可能性。但是,对于GC算法而言,如何准确高效的进行内存清理是一个复杂的过程。随着业务需求的不断发展,GC算法也在不停地迭代,只有针对不同的业务目标,选择*合适的GC算法,才能够更好的帮助业务实现其目标。近几年,随着服务器硬件性能越来越强劲,其软件应用往往也需要更大的堆,从10G到100G,甚至TB级别。在这种环境下,传统的CMS、G1等GC算法,其停顿时间往往随着堆大小的增长而增加,对于超大堆在触发Full GC的时候,甚至可能产生分钟级别的停顿,这样对于延迟敏感的应用来说,GC 停顿已经成为阻碍 其广泛应用的一大顽疾,需要更适合的 GC 算法以满足这些业务的需求。

ZGC 是由JEP333引入JDK,希望彻底解决GC停顿带来的延迟问题,其设计目标为:每次GC停顿时间控制在10ms以下;相对于G1 GC,吞吐率下降不超过15%;支持大堆和特大堆,并且停顿时间不随着堆大小的增长而增长。ZGC从 JDK11 开始推出实验性版本,并随着JDK新版本发布不断补充完善,*终在JDK15中成为正式版本,保证了 Java 停顿时间不会随着堆大小和业务规模的增加而增长。为对GC停顿要求高的业务提供了一种更好的选择。

%title插图%num

图 1 ZGC性能(出自The Design of ZGC,Per Lidén)

KonaJDK团队为了满足业务的需求,在Tencent Kona JDK11版本中,完善了ZGC功能的补全,并进行了长期的验证落地,使得对GC停顿敏感的业务也能够在JDK11版本中满足对于低GC延迟的需求。JDK11在2018 年下半年发布,属于Long-Term Support版本,而后续LTS版本为JDK17,预计将于2021年9月发布,中间其他版本属于过渡开发版本,没有持续的更新和修复。因此,KonaJDK团队选择在JDK11完善ZGC的功能,满足业务的需要,即使后续JDK17发布之后,业务版本更新也需要一个过程,在这期间,仍然需要JDK11的支持。

对于Arm架构而言,在JDK11支持ZGC相对于x86架构是一个更大的挑战。x86架构从JDK11开始ZGC就作为Experimental特性开始发布,但是在Arm架构,从JDK13才有对于ZGC的支持。KonaJDK团队进行了大量的工作完成了Arm架构在JDK11中对于ZGC的支持:

  • 需要选择JDK在Arm架构中合适的提交移植到JDK11版本
  • 从JDK11到JDK13,ZGC代码以及Hotspot代码经过了多次重构,在代码移植过程中需要分析代码重构的功能以及影响,或者移植相关重构代码,或者根据JDK11对相关代码进行适配修改
  • 根据Arm架构的特征,适配团队对于ZGC的优化、功能增强以及Bug修复。
  • Arm属于RISC架构,而且使用弱有序内存模型,因此在适配相关汇编代码(特别是ZGC使用的barrier)时,需要仔细斟酌指令的选择,在保证正确性的基础上尽可能的降低开销,提高效率
  • 在Arm平台进行充分、全面的测试,保证相关代码的健壮性

KonaJDK团队在Arm结构支持ZGC的过程中,遇到的*大困难在于如何正确添加barrier指令保证正确性。由于Arm使用弱有序内存模型,在x86平台能够正确执行的代码在Arm架构下可能由于缺少必要的barrier导致产生随机错误。KonaJDK团队在初步完成ZGC支持代码之后,进行ZGC压力测试过程中,发现存在执行若干次GC之后,存在JDK随机崩溃的现象,发生几率几千分之一。通过对错误现场的分析,大概率怀疑是缺少必要的barrier所致。尝试通过对社区代码以及ZGC逻辑对问题进行分析,在这个过程中,JDK13和JDK11代码结构的不同进一步加大了分析的难度,*终KonaJDK团队完成该问题的修复,ZGC代码在Arm架构连续运行数百万次无问题。

和其他GC算法一样,ZGC也有其适用的业务场景。ZGC算法*大的优势是能够将停顿时间控制在10ms以下,特别适合对于停顿时间敏感的业务。但是为了实现如此短的停顿时间,ZGC的代价是一部分性能损失和内存消耗。ZGC通过将若干任务进行并发化改造,使得若干之前必须在停顿时完成的工作,可以和应用代码并发执行,有效的降低了必须的停顿时间。但是这种并发执行,以及其引入的各类Barrier,也会导致一定程度的应用吞吐率下降。通过整个 OpenJDK 社区的持续投入,当前 ZGC 在性能损失场景中的性能下降已经控制在很小的范围内。对于性能来说,如充足的内存下即大堆场景,ZGC 在各类 Benchmark 中能够超过 G1 大约 5% 到 20%,而在小堆情况下,则要低于 G1 大约 10%。

因此,不同的业务需要根据实际的情况,选择更为合适的GC算法,来保证吞吐率和停顿时间都能够满足业务的需求。目前来看,如果业务应用使用了超大堆(几十G甚至上百G)为了避免传统G1等GC算法Full GC时带来的几十秒甚至分钟级别的停顿,建议使用ZGC。另外如果业务对于停顿时间的有着严格的时限要求,那么也建议使用ZGC。

KonaFiber

应用在需要并发执行多项任务的时候,会创建多个线程,每个线程负责一项任务,从而实现任务的并发执行。但是,随着业务规模的不断增大,如果仍然为每一个任务创建一个线程,由于线程本身内存消耗较大,会导致占用大量的内存。另外,线程切换需要内核完成,大量的线程存在时,其频繁的切换开销也会影响并发执行的效率。协程就是为了解决这种情况而诞生的。协程是一种轻量级的线程,兼顾开发效率和执行效率。协程的切换在用户态完成,比线程切换开销小很多,同时对于内存的需求更低,相对的需要应用代码编写时关注部分协程切换的工作。协程相对于线程,在高并发场景能够取得更好的性能,应用越来越广泛。OpenJDK也启动了Java协程原生支持项目:Project Loom,开发时间超过3.5年,并在不断发展完善,即将成为Experimental特性。

KonaFiber是KonaJDK团队实现的协程方案,它在兼容OpenJDK社区Loom API的同时,提供了更好的切换性能,不过需要部分额外的内存开销。KonaFiber根据业务的需要,目前在JDK8和JDK11实现,和社区兼容的API使它成为可以和社区方案一起长期演进的协程方案。目前KonaFiber已经完成对于Arm架构的支持,能够满足Arm架构应用对于协程的需求。

%title插图%num

图 2 KonaJDK和Loom对比

为了满足业务的需求,提供更好的协程切换性能,KonaFiber采用了基于JKU的StackFul有栈方案,为每一个协程创建独立的堆栈。当进行协程切换的时候,JDK在对于协程Pin状态检测以及上下文保存之外,只需要修改Frame Pointer和Stack Pointer的值就可以完成协程的切换工作,逻辑简单且性能开销很小。不过相对于社区的方案,KonaFiber的StackFul方案对于内存的使用要多一些,更适用于对于内存消耗不太敏感,但是对于性能更敏感的业务场景。性能数据如图 2所示,左图表示在不同协程数量情况下,每秒内协程切换次数对比;右图对内存消耗进行了对比。

%title插图%num

图 3 KonaFiber性能对比

KonaFiber的实现注重优化以及代码重构,通过多种方式不断进行优化:

  • 协程轻量化,不断优化降低协程的资源消耗
  • 按需创建,根据业务的需要创建协程,降低内存使用
  • GC优化,优化实现,降低协程对GC引入的开销
  • 稳定性修复,通过广泛的测试以及业务适配,提高健壮性

相对于OpenJDK社区的协程方案Loom,KonaFiber提供了更高更稳定的调度性能。图 3对比了KonaFiber和Loom在不同协程数量情况下的每秒调度次数。

%title插图%num

图 4 调度性能对比

目前KonaFiber在KonaJDK8中已经开源,后续也会在KonaJDK11中开源,KonaJDK也会持续跟进Loom社区并不断完善KonaFiber的实现。

OWST优化

GC运行过程中,存在若干GC线程并行处理各种任务,但是不同任务的处理时间不等,使得各个GC线程之间负载分配并不平衡。JDK中通过如下的方式来平衡各个GC线程之间的负载,降低GC的停顿时间:当一个GC线程执行完成它被分配的任务之后,会查看其它GC线程的任务队列,如果存在这个线程可以执行的任务,那么它会将该任务“偷取”过来并执行。该过程持续循环直到GC结束。该方案实现了负载的自动平衡,但是执行过程中,由于可能多个GC线程同时“偷取”任务,在线程数量较多时,锁的竞争会比较激烈,同时抢锁过程中,各个GC线程的自旋等待也会导致一定的性能开销,使得该算法实际表现差强人意。

为了优化这个过程,Google在ISMM 2016发表了的论文提出了一种新的负载均衡算法:Optimized Work-Stealing Threads(OWST)。该算法的基本思想是:当存在多个GC线程需要“偷取”任务时,*终只有一个线程执行“偷取”操作,其它线程进入等待状态。执行“偷取”操作的线程检查各个GC线程的任务队列,根据任务个数唤醒线程,并执行任务。算法有效的降低了各个GC线程之间对于锁的竞争,提高了整个负载均衡的效率。

OpenJDK社区首先在Shenandoah GC上实现了OWST算法,在JDK12版本中合入主干分支并成为默认的Parallel Terminator。为了更好地支持LTS版本,KonaJDK团队将OWST算法相关代码移植到JDK8和JDK11,并完成相关代码适配和测试工作,经过业务端验证,为JDK8和JDK11添加了商用的OWST算法支持,有效降低了GC并行任务的执行时间,降低了GC的停顿时间。

通过对SPECjbb2015的性能进行测试,使用ParallelGC时OWST在对于max-jOPS基本没有影响的前提下,能够提升大约8%的critical-jOPS评分。另外对于腾讯内部大数据相关的Map/Reduce以及Spark SQL任务进行测试,执行性能也有10+%的提升。

业务应用

KonaJDK

目前在Arm架构,ZGC已经在腾讯的大规模生产中得到了实践应用。

ZGC将停顿时间控制在10ms以下的特性,使得它特别适合停顿时间敏感的业务。腾讯的WAF团队使用Java语言来快速实现产品功能迭代及上线。该团队有一个旁路安全服务,是一个基于Netty框架的Http服务。它对于端到端请求的时延要求特别严格,需要达到99.99% 请求时延小于 80ms 的 SLA 目标。传统的GC算法,难以达到如此高的标准,较长的停顿时间对于该服务有一定的负面影响,需要寻找一种更低停顿时间的GC算法。WAF团队之前采用了G1 GC算法,花费了大量的时间和精力对G1 GC进行选项调优以及代码层面的修改,但由于G1GC本身的不足,仍然存在请求抖动延迟,无法达到既定的 SLA 目标。后续在KonaJDK团队的配合下,通过切换ZGC算法,实现了该业务的P9999 请求延迟稳定小于 80ms,为用户提供了更快速、稳定的服务。

后续计划

KonaJDK

目前KonaJDK团队在Arm架构,主要在JDK8和JDK11版本进行优化和支撑,后续也会支撑JDK17等版本。KonaJDK团队会持续对JDK基础类库、运行时、内存管理、执行引擎等等各个模块进行分析和测试,不断扩展JDK的功能,提升性能。

KonaJDK团队会始终将Arm架构作为重点支撑平台,不断加大投入,推动并完善JDK对于Arm架构的支持,满足对于Arm架构不断增长的需求。