主线程和子线程
主线程是指进程中拥有的线程,在Java中默认情况下一个进程只有一个线程,这个线程就是主线程。主线程主要处理界面交互相关的逻辑。因为用户随时会和界面发生交互,因此主线程在任何时候都必须有较高的响应速度,否则就会产生一种页面卡顿的感觉。为了保持较高的响应速度,这就要求主线程中不能执行耗时的任务,就需要子线程来完成这些任务。子线程也叫工作线程,除了主线程以外的线程都是子线程。
Android沿用了Java的线程模型,其中的线程也分为主线程和子线程,其中UI线程就是主线程。主线程的作用是运行四大组件以及处理他们和用户的交互,而子线程的作用则是耗时任务,比如网络请求,I/O操作等。从Android 3.0开始要求网络访问必须在子线程中进行,否则网络访问会失败并会抛出NetworkOnMainThreadException异常。这样做是为了避免主线程由于耗时操作从而出现ANR现象。
Android中的线程形态
在Android中我们开启子线程最简单方法就是new Thread,实现run方法,执行耗时操作。除了Thread之外,Android系统还封装了Thread具有特殊表现形式的线程。针对不用使用场景和功能分为:AsyncTask,HandlerThread,IntentService。这三者底层实现的都是线程,同时在使用上也各有优缺点。
AsyncTask
这部分请参考:AsyncTask异步任务机制源码分析和总结笔记
HandlerThread
HandlerThread继承了Thread,它是一种可以使用Handler的Thread,它的实现很简单,就是在run方法中通过Looper.prepare()来创建消息队列,并通过Looper.loop()来开启消息循环,这样在实际使用中就允许在HandlerThread中创建Handler了。
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
HandlerThread的实现来看,他和普通的Thread有显著的不同之处,普通的Thread主要用于在run方法中执行一个耗时任务,而HandlerThread在内部创建了消息队列,外界需要通过Handler的消息方式;来通知HandlerThread执行一个具体的任务。由于HandlerThread的run方法是一个无限循环,因此当明确不需要再使用HandlerThread时,可以通过他的quit或者quitSafely方法来终止线程的执行,这是一个良好的编程习惯。
使用范例:
HandlerThread mHandlerThread = new HandlerThread("Test", 5);
mHandlerThread.start();
mHandler = new Handler(mHandlerThread.getLooper());
}
private Runnable mRunnable = new Runnable() {
@Override
public void run() {
while (mRunning) {
Log.d("MainActivity", "test HandlerThread...");
try {
Thread.sleep(200);
} catch (Exception e) {
e.printStackTrace();
}
}
}
};
IntentService
Intent是一种特殊的Service,他继承了Service并且他是一个抽象类,因此必须创建他的子类才能使用IntentService。IntentService可用于执行后台耗时的任务,当任务执行完成后它会自动停止,同事由于IntentService是服务的原因,导致它的优先级比单纯的线程要高得多,所以IntentService比较适合执行一些高优先级的后台任务,以内他优先级高不容易被杀死。实际上,IntentService封装了Handler和HandlerThread。在onCreate方法中。
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
当IntentService被第一次启动时,它的onCreate方法会被调用,onCreate方法会创建一个HandlerThread,然后使用它的Looper来构造一个Handler对象mServiceHanlder,这样通过mServiceHandler发送的消息最终都会在HandlerThread中执行。每次启动IntentService,它的onStartCommand方法就会被调用一次,IntentService在onStartCommand中处理每个后台任务的Intent。
/**
* You should not override this method for your IntentService. Instead,
* override {@link #onHandleIntent}, which the system calls when the IntentService
* receives a start request.
* @see android.app.Service#onStartCommand
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
在onStartCommand方法中调用了onStart方法,在onStart方法中可以了看出,IntentService仅仅是通过mServiceHandler发送了一个消息,这个消息会在Handlerthread中被处理。mServiceHandler收到消息后,会将Intent对象传递给onHandlerIntent方法中去处理。注意这个Intent对象的内容和外界的startService(intent)中intent的内容是完全一致的。通过这个Intent对象即可解析出外界启动IntentService是所传递的参数,通过这些参数就可以区分具体的后台任务,这样在onHandlerIntent方法中就可以对不同的后台任务做出来了。
当onhandlerIntent方法执行结束后,Intent会通过stopSelf(int startId)方法来尝试停止服务,而这个时候之所以采用stopSelf(int startId)而不是stopSelf()来停止服务,那是因为stopSelf()会立刻停止服务,而这个时候可能还有其他的消息未处理,stopSelf(int startId)则会等待所有的消息都处理完毕后才停止服务。
一般来说,stopSelf(int startId)在尝试停止服务之前会判断最近启动的服务次数是否和startId相等,如果相等就立刻停止服务,不相等则不停止服务。这个策略可以从AMS的stopServiceToken方法的实现找到依据。
Android中的线程池
线程池在Android开发中使用的优势特别明显,主要可以概括为三点:
(1) 重用线程池中的线程,避免因为线程的创建和销毁所带来的性能开销
(2) 能够有效控制线程池的最大并发数,避免大量的线程之间因相互抢占系统资源而导致的阻塞现象。
(3) 能够对线程进行简单的管理,并提供定时执行以及制定间隔循环执行等功能
Android中线程池的概念源于Java中的Executor,Executor是一个接口,真正的线程池的实现为ThreadPoolExecutor提供了一系列参数来配置线程池,下面介绍ThreadPoolExecutor的构造方法中的各个参数的含义,这些参数将直接影响到线程池的功能特性,下面是ThreadPoolExecutor的一个比较常用的构造方法。
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters and default thread factory.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code handler} is null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
corePoolSize
线程池的核心线程数,默认情况下,核心线程会在线程池中一直存活,即时他们处于闲置状态。如果将ThreadPoolExecutor的aollwThreadTimeOut属性设置为true,那么闲置的核心线程在等待新任务到来时会有超时策略,这个时间间隔有keepAliveTime所指定,当等待时间超过keepAliveTime所指定的时长后,核心线程就会被终止。
maximumPoolSize
线程池所能容纳的最大线程数,当活动线程数达到这个最大值后,后续的新任务将会被阻塞。
keepAliveTime
非核心线程闲置是的超时时长,超过这个时长,非核心线程就会被回收。当ThreadPoolExecutor的allowCoreThreadTimeOut属性设置为true时,keepAliveTime同样会作用于核心线程。
unit
用于指定keepAliveTime参数的时间单位,这是一个枚举,常用的有TimeUnit.MILLISECONDS(毫秒),TimeUnit.SECONDS(秒),以及TimeUnit.MINUTES(分钟)等
workQueue
线程池中的任务队列,通过线程池的executor方法提交的Runnable对象会存储在这个参数中。
threadFactory
线程工厂,为线程池提供创建新线程的功能。ThreadFactory是一个接口,他只是一个方法:Thread newThread(Runnable r)
ThreadPoolExecutor执行任务是大致遵循如下规则:
(1) 如果线程池中的线程数量未达到核心线程的数量,那么会直接启动一个核心线程来执行任务。
(2) 如果线程池中的线程数量已经达到或者超过核心线程的数量,那么任务会被插入到任务队列中排队等待执行。
(3) 如果在步骤2中无法将任务插入到任务队列中,这往往是由于任务队列已满,这个时候如果线程数量未达到线程池规定的最大值,那么会立刻启动一个非核心线程来执行任务。
(4) 如果步骤3中线程数量已达到线程池规定的最大值,那么就拒绝执行此任务,
下面我们以AsyncTask配置为例来进行分析
public abstract class AsyncTask<Params, Progress, Result> {
private static final String LOG_TAG = "AsyncTask";
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;
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);
从上面的代码可以看出,Asynctask对ThreadPoolExecutor线程池的配置规格
1, 核心线程数等于CPU核心数+1;
2, 线程池的最大线程数等于CPU核心数的2倍+1;
3, 核心线程无超时机制,非核心线程在限制时的超时时间为1秒
4, 任务队列的容量为128;
线程池的分类
1, FixedThreadPool
线程数量固定的线程池,当线程处于空闲状态时,他们并不会被回收,除非线程池被关闭了。FixedThreadPool只有核心线程并且这些核心线程不会被回收,意味着它能更快速的响应外界的请求。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
}
2, CachedThreadPool
一种线程数量不定的线程池,它只有非核心线程,并且最大的线程数为Integer.MAX_VALUE。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L,
TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}
3, ScheduledThreadPool
核心线程数固定,而非核心线程数没有限制。并且当非核心线程闲置时会被立即回收。这类线程池主要用于执行定时任务和具有固定周期的重复任务。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize){
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* Creates a new {@code ScheduledThreadPoolExecutor} with the
* given core pool size.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @throws IllegalArgumentException if {@code corePoolSize < 0}
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
4, SingleThreadPool
线程池内部只有一个核心线程,它确保所有的任务都在同一个线程中按顺序执行。SingleThreadExecutor的意义在于统一所有外界任务到一个线程中,使得在这些任务之间不需要处理线程同步的问题(顺序执行)。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>()));
}