深入理解Android多线程、线程同步及AsyncTask机制

Android应用中的线程

在Android Studio中通过adb shell命令可以查看应用的进程与线程信息,操作之前,先明确几个概念:

  • UID——User ID,即用户id,在Android中,每个应用就代表一个用户,用户id在应用安装后就会分配。
  • PID——Process identifier,即进程id。
  • PPID——Parent process identifier,即父进程id,我们知道每个应用进程都派生自另一个进程,而Android中所有应用进程都派生自Zygote进程,也就是说Zygote进程是所有应用的父进程,待会儿我们也会证明这一事实。
    使用adb命令进行如下操作:

    1. 先查看Zygote的进程情况
    2. 再来看me.geed.planner应用的进程情况
    3. 最后打印me.geed.planner应用的线程信息
      线程
      其中的com.feoul.customviewdemo是应用包名
      上图的第一列代表UID,第二列为PID,第三列为PPID,最后一列为Name。因此我们可以发现Zygote进程的PID为365。
      则可以看出线程名为:com.feoul.customviewdemo的线程即是应用的UI线程,因为此线程的PPID正是365(即zygote进程),往下的线程都是UI线程的子线程,,如Heap线程、GC线程、Signal Catcher线程、Compiler线程及Binder线程等。

我们经常用到的子线程即为工作线程,用来执行一些耗时的任务,比如网络请求,I/O操作等。

Android中线程同步的方法:

  1. synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中。
    在资源竞争不是很激烈的情况下,Synchronized的性能要优于
  2. ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
    ReentrantLock获取锁定与三种方式:
    a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
    b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
    c)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
    d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
  3. Atomic: 和ReetrantLock 的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。

在Android中除了传统的用New Thread的方式创建子线程外,Android SDK 也为我们封装了一些子线程,方便我们使用,比如AsyncTask,HandlerThread以及IntentService,它们的底层实现也是线程,下面我们来重点了解下AsyncTask的工作原理。

AsyncTask机制

先来看看AsyncTask的定义:

public abstract class AsyncTask<Params, Progress, Result> 

三种泛型类型分别代表“启动任务执行的输入参数”、“后台任务执行的进度”、“后台计算结果的类型”。在特定场合下,并不是所有类型都被使用,如果没有被使用,可以用Java.lang.Void类型代替。
一个异步任务的执行一般包括以下几个步骤:
1.execute(Params… params),执行一个异步任务,需要我们在代码中调用此方法,触发异步任务的执行。
2.onPreExecute(),在execute(Params… params)被调用后立即执行,一般用来在执行后台任务前对UI做一些标记,及做一些准备工作。
3.doInBackground(Params… params),在onPreExecute()完成后立即执行,用于执行较为费时的操作,此方法将接收输入参数和返回计算结果。在执行过程中可以调用publishProgress(Progress… values)来更新进度信息。另外此方法需要返回计算结果给onPostExecute方法。
4.onProgressUpdate(Progress… values),在调用publishProgress(Progress… values)时,此方法被执行,直接将进度信息更新到UI组件上。
5.onPostExecute(Result result),当后台操作结束时,此方法将会被调用,计算结果将做为参数传递到此方法中,直接将结果显示到UI组件上。

Tip:在使用的时候,有几点需要格外注意:
1.异步任务的实例必须在UI线程中创建。
2.execute(Params… params)方法必须在UI线程中调用。
3.不要手动调用onPreExecute(),doInBackground(Params… params),onProgressUpdate(Progress… values),onPostExecute(Result result)这几个方法。
4.不能在doInBackground(Params… params)中更改UI组件的信息。
5.一个任务实例只能执行一次,如果执行第二次将会抛出异常。

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


@MainThread
    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();

        mWorker.mParams = params;
        exec.execute(mFuture);

        return this;
    }

由 mStatus = Status.RUNNING; 可知,,每个异步任务在完成前只能执行一次。否则会报错。
然后执行了onPreExecute(),当前依然在UI线程,所以我们可以在其中做一些准备工作。
上面出现了mWorker和mFuture两个未知的类,我们来探究下

public AsyncTask() {  
        mWorker = new WorkerRunnable<Params, Result>() {  
            public Result call() throws Exception {  
                mTaskInvoked.set(true);  

                Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);  
                //noinspection unchecked  
                return postResult(doInBackground(mParams));  
            }  
        };  
...

}  

可以看到mWorker在构造方法中完成了初始化,并且因为是一个抽象类,在这里new了一个实现类,实现了call方法,call方法中设置mTaskInvoked=true,且最终调用doInBackground(mParams)方法,并返回Result值作为参数给postResult方法.可以看到还调用了doInBackground方法。

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

有此处可知,AsyncTask的底层的线程切换,还是用到了我们上一篇文章讲到的Handler机制,传递了一个消息message, message.what为MESSAGE_POST_RESULT;message.object= new AsyncTaskResult(this,result);
其中AsyncTaskResult就是一个简单的携带参数的对象。
继续查看getHandler:

private static Handler getHandler() {
        synchronized (AsyncTask.class) {
            if (sHandler == null) {
                sHandler = new InternalHandler();
            }
            return sHandler;
        }
    }

InternalHandler:

private static class InternalHandler extends Handler {
        public InternalHandler() {
            super(Looper.getMainLooper());
        }

        @SuppressWarnings({"unchecked", "RawUseOfParameterizedType"})
        @Override
        public void handleMessage(Message msg) {
            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:
                    result.mTask.onProgressUpdate(result.mData);
                    break;
            }
        }
    }

可以看到,在接收到MESSAGE_POST_RESULT消息时,执行了result.mTask.finish(result.mData[0]);其实就是我们的AsyncTask.this.finish(result),于是看finish方法

private void finish(Result result) {
        if (isCancelled()) {
            onCancelled(result);
        } else {
            onPostExecute(result);
        }
        mStatus = Status.FINISHED;
    }

此处判断了是否调用了cancel(),若无则调用 onPostExecute(result);方法,最后将状态置为FINISHED。

还记得上面的execute方法中的倒数第2行:exec.execute(mFuture)
exec为executeOnExecutor(sDefaultExecutor, params)中的sDefaultExecutor
下面看这个sDefaultExecutor

private static volatile Executor sDefaultExecutor = SERIAL_EXECUTOR;  
public static final Executor SERIAL_EXECUTOR = new SerialExecutor();  
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() {  
                public void run() {  
                    try {  
                        r.run();  
                    } finally {  
                        scheduleNext();  
                    }  
                }  
            });  
            if (mActive == null) {  
                scheduleNext();  
            }  
        }  
        protected synchronized void scheduleNext() {  
            if ((mActive = mTasks.poll()) != null) {  
                THREAD_POOL_EXECUTOR.execute(mActive);  
            }  
        }  
}  

可以看到sDefaultExecutor其实为SerialExecutor的一个实例,其内部维持一个任务队列;直接看其execute(Runnable runnable)方法,将runnable放入mTasks队尾;
判断当前mActive是否为空,为空则调用scheduleNext方法
scheduleNext,则直接取出任务队列中的队首任务,如果不为null则传入THREAD_POOL_EXECUTOR进行执行。
第一个任务入队,然后在mActive = mTasks.poll()) != null被取出,并且赋值给mActivte,然后交给线程池去执行。然后第二个任务入队,但是此时mActive并不为null,并不会执行scheduleNext();所以如果第一个任务比较慢,10个任务都会进入队列等待;真正执行下一个任务的时机是,线程池执行完成第一个任务以后,调用Runnable中的finally代码块中的scheduleNext,所以虽然内部有一个线程池,其实调用的过程还是线性的。一个接着一个的执行,相当于单线程。

下面看THREAD_POOL_EXECUTOR:


    /**
     * An {@link Executor} that can be used to execute tasks in parallel.
     */
    public static final Executor THREAD_POOL_EXECUTOR;

    static {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
                sPoolWorkQueue, sThreadFactory);
        threadPoolExecutor.allowCoreThreadTimeOut(true);
        THREAD_POOL_EXECUTOR = threadPoolExecutor;
    }

    /**

可以看到就是一个自己设置参数的线程池,

private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
// the CPU with background work
    private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
    private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
    private static final int KEEP_ALIVE_SECONDS = 30;

参考资料:
http://blog.csdn.net/lmj623565791/article/details/38614699
http://blog.csdn.net/ahence/article/details/50768587

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值