Android学习笔记:多个AsyncTask实例的并发问题

AsyncTask是Android给开发者提供的一个简单轻量级的多线程类,通过它我们可以很容易新建一个线程让在后台做一些耗时的操作(如IO操作、网络访问等),并在这个过程中更新UI。之所以说它轻量级,是因为不需要直接使用Handler、Thread等知识,使用起来比较简单,但也失去了一些灵活性,对于一些复杂的场景处理起来不方便。

如果一个APP进程中同时只创建和运行一个AsyncTask实例,则不会有任何问题。但如果在一个进程中如果有多个AsyncTask任务同时在执行,问题就比较复杂了。下面我们通过例子来看(我们例子是在Android 4中运行的)。

一、测试1(默认多个Task是串行执行的)

1、创建一个默认的app工程

2、创建一个类继承AsyncTask,代码如下

package com.example.asynctaskdemo;

import android.os.AsyncTask;

public class MyAsyncTask extends AsyncTask<String, Void, String>{
	
	private String name;
	
	public MyAsyncTask(String name){
		this.name = name;
	}

	@Override
	protected String doInBackground(String... params) {
		System.out.println(name+" is run "+System.currentTimeMillis()+" thread id "+Thread.currentThread().getId());
		try {
			Thread.sleep(1000*10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		return null;
	}

}

3、在Activity的onCreate方法中使用该Task,代码如下

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		
		for(int i=1;i<5;i++){
			MyAsyncTask task = new MyAsyncTask("task"+i);
			task.execute(new String[0]);
		}
	}

  我们在调试窗口,观察MyAsyncTask打印信息的间隔和顺序。发现这创建的4个任务是串行执行的,并不是并发的。

研究了AsyncTask的实现细节,在创建一个AsyncTask并通过其execute方法启动执行时,AsyncTask并不是创建一个独立的线程去执行。AsyncTask是通过线程池来管理和调度进程中的所有Task的。

在 Android2.3以前的版本(SDK/API 大于等于10的版本)

多个AsyncTask任务是并发执行的,也就是说如果启动多个task,则会并发执行。但并发执行的数量取决于AsyncTask内部的线程池限制数量。如果超过了这个限额,新的任务只能等待。

 在Android 3.0及以后版本(SDK/API 大于等于11的版本)

Google从Android 3.0开始对AsyncTask的调度执行做出了一些变化,对于execute提交的任务,按先后顺序每次只运行一个。也就是说它是按提交的次序,每次只启动一个线程执行一个任务,完成之后再执行第二个任务,也就是相当于只有一个后台线程在执行所提交的任务。上面的例子就验证了这一点。

二、让AsyncTask并发执行

因为默认情况下多个task是串行的,那怎么样让并发执行呢?AsyncTask增加了一个新的接口executeOnExecutor,这个接口允许开发者提供自定义的线程池来运行和调度Thread。我们把上面代码改下:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		for(int i=1;i<11;i++){
			MyAsyncTask task = new MyAsyncTask("task"+i);
			//task.execute(new String[0]);
			task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, new String[0]);
		}
	}

  这里我们使用了executeOnExecutor方法代替了execute方法。并且executeOnExecutor方法的第一个参数是一个预定义的线程池。这时这几个task就可以并发执行了。这时我们观察打印的结果,发现有5个任务并发执行,可以看出有5个不同的线程号,查看AsyncTask的源码,发现并发线程数跟设备的cpu数量是有关的,因此不同的设备上可能看到的结果不完全一致,这点需要注意。只有前面的5个任务执行完后,才会执行后面的,并且通过打印的线程号可以看出,后面执行的任务是重用原来的线程,并没有创建新的线程,这就是线程池的作用。

我们再来改下代码,使用Android提供的另外一个预定义的线程池。代码如下:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		for(int i=1;i<11;i++){
			MyAsyncTask task = new MyAsyncTask("task"+i);
			//task.execute(new String[0]);
			task.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR, new String[0]);
		}
	}

观察打印信息我们可以发现,这和调用execute方法一样,每个任务都是串行执行的。并且这个过程中最多创建了5个新的线程。

三、自定义线程池

我们可以利用java.util.concurrent.Executors中的各种静态方法创建供AsyncTask执行的线程池 ,可以指定线程的数量和调度的方式。其方法很多,我们这里介绍其中两种较为常用的。

1、让每个AsyncTask任务都单独起一个线程执行,也就是说所有的都是并发的。代码如:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
		for(int i=1;i<11;i++){
			MyAsyncTask task = new MyAsyncTask("task"+i);
			task.executeOnExecutor(newCachedThreadPool, new String[0]);
		}
	}

  通过观察打印可以看出,这多个任务都是并发执行的。

2、创建指定线程数量的线程池,并发数上限就是指定的线程数。但新任务产生,没有空闲的线程,就只能等待。代码如:

@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		ExecutorService newFixedThreadPool = Executors.newFixedThreadPool(3);
		for(int i=1;i<11;i++){
			MyAsyncTask task = new MyAsyncTask("task"+i);
			task.executeOnExecutor(newFixedThreadPool, new String[0]);
		}
	}

  通过观察可以看出,有3个线程在并发执行。

总结下,从上面的例子中可以看出,如果一个进程中存在多个TASK需要并发执行的情况,那就需要用到AsyncTask一些更深的知识,需要考虑的问题更多。

 

郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。