Android多线程笔记

作者: hloong | 来源:发表于2016-09-19 17:26 被阅读267次

    消息机制

    处理消息的手段--Handler,Looper与MessageQueue

    =tips:=

    1. 子线程无法更新UI,所以需要通过Handler将一个消息Post到UI线程中(该Handler必须在主线程中创建),
      为什么?
      每个Handler都会关联一个消息队列,消息队列被封装在Looper中,每个Looper又回关联一个线程(Looper通过ThreadLocal封装),最终就等于每个消息队列会关联一个线程。

    2. Handler就是一个消息处理器,将消息投递给消息队列,然后再由对应的线程从消息队列中逐个取出消息,并且执行。默认情况下,消息队列只有一个,即主线程的消息队列,这个消息队列是在ActivityThread.main方法中创建的,通过Looper.prepareMainLooper()来创建,最后执行Looper.loop()来启动消息循环。

    3. 那么Handler是如何关联消息队列以及线程的呢?
      Handler会在内部通过Looper.myLooper()来获取Looper对象,并且与之关联,最重要的就是消息队列

    4. 消息队列通过Looper与线程关联上,Handler与Looper关联;
      Handler要与主线程的消息队列关联上,这样handlerMessage才会执行在UI线程,此时更新UI才是线程安全的!

    5. 消息循环的建立是通过Looper.loop()这个方法。

    Looper总结:
    通过Looper.prepare()来创建Looper对象(消息队列封装在Looper对象中),并且保存在sThreadLoal中,然后通过Looper.loop()来执行消息循环。

    Handler最终将消息追加到MessageQueue中,而Looper不断地从
    MessageQueue中读取消息,并且调用Handler的dispatchMessage消息,
    这样消息就源源不断地被产生,添加到MessageQueue,被handler处理,
    这样Android引用就运转起来了
    

    在子线程中创建Handler为何会抛出异常?

        new Thread(){
            Handler handler = null;
            public void run(){
                handler = new Handler();
            }
        }.start();
    

    上述代码的问题?
    在Handler源码中,Looper对象是ThreadLocal的,每个线程都有自己的Looper,Looper可以为空,但是当子线程中创建Handler对象时,如果Looper为空,那么就会抛出异常
    Handler源码中有判断:

       public Handler(Callback callback, boolean async) {
            if (FIND_POTENTIAL_LEAKS) {
                final Class<? extends Handler> klass = getClass();
                if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                        (klass.getModifiers() & Modifier.STATIC) == 0) {
                    Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                        klass.getCanonicalName());
                }
            }
    
            mLooper = Looper.myLooper();
            if (mLooper == null) {
                throw new RuntimeException(
                    "Can't create handler inside thread that has not called Looper.prepare()");
            }
            mQueue = mLooper.mQueue;
            mCallback = callback;
            mAsynchronous = async;
        }
    

    修改:

        new Thread(){
            Handler handler = null;
            public void run(){
                //1,为当前线程创建Looper,绑定到ThreadLocal中
                Looper.prepare();
                handler = new Handler();
                //2,启动消息循环
                Looper.loop();
            }
        }.start();
    

    多线程

    Android中的多线程就是JAVA中的多线程,Java中的线程详解:http://www.cnblogs.com/riskyer/p/3263032.html
    为了方便,Android封装了一些类,如:AsyncTask,HandlerThread等。

    Runnable和Thread有什么区别?

    看Thread的源码,Thread实现了Runnable接口,Thread里最终被线程执行的任务是Runnable,而非Thread。
    Thread只是对Runnable的包装,并且通过一些状态对Thread进行管理与调度。
    Runnalbe接口定义了可执行的任务,它只有一个无返回值的run()函数。

    线程的状态

    线程有四种状态,任何一个线程肯定处于这四种状态中的一种:

    1. 产生(New):线程对象已经产生,但尚未被启动,所以无法执行。
      如通过new产生了一个线程对象后没对它调用start()函数之前。
    2. 可执行(Runnable):每个支持多线程的系统都有一个排程器,排程器会从线程池中选择一个线程
      并启动它。当一个线程处于可执行状态时,表示它可能正处于线程池中等待排排程器启动它;
      也可能它已正在执行。如执行了一个线程对象的start()方法后,线程就处于可执行状态,
      但显而易见的是此时线程不一定正在执行中。
    3. 死亡(Dead):当一个线程正常结束,它便处于死亡状态。
      如一个线程的run()函数执行完毕后线程就进入死亡状态。
    4. 停滞(Blocked):当一个线程处于停滞状态时,系统排程器就会忽略它,不对它进行排程。
      当处于停滞状态的线程重新回到可执行状态时,它有可能重新执行。如通过对一个线程调用wait()函数后,
      线程就进入停滞状态,只有当两次对该线程调用notify或notifyAll后它才能两次回到可执行状态。

    线程的wait、sleep、join和yield(面试必备)

    1. wait()
      让当前的线程等待,直到其他线程调用此对象的 notify()方法或 notifyAll()方法。
      使当前线程暂停执行并释放对象锁标示,让其他线程可以进入synchronized数据块,
      当前线程被放入对象等待池中。当调用notify()方法后,
      将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,
      只有锁标志等待池中线程能够获取锁标志;如果锁标志等待池中没有线程,则notify()不起作用。
      notifyAll()则从对象等待池中移走所有等待那个对象的线程并放到锁标志等待池中。
    2. sleep()
      使当前线程(即调用该方法的线程)暂停执行一段时间,让其他线程有机会继续执行,但它并不释放对象锁。也就是说如果有synchronized同步快,其他线程仍然不能访问共享数据。注意该方法要捕捉异常。
    3. join()
      join()方法使当前线程停下来等待,直至另一个调用join方法的线程终止。线程在被激活后不一定马上就运行,而是进入到可运行线程的队列中。但是join()可以通过interrupt()方法打断线程的暂停状态,从而使线程立刻抛出InterruptedException。
    4. yield()
      Yield()方法是停止当前线程,让同等优先权的线程运行。如果没有同等优先权的线程,那么Yield()方法将不会起作用。
    线程方法 是否释放同步锁 是否需要在同步的代码块中调用 方法是否已废弃 是否可以被中断
    sleep()
    wait()
    suspend 1.6废弃 是
    resume() 1.6废弃 是
    join()

    扩展:http://zheng12tian.iteye.com/blog/1233638
    http://dylanxu.iteye.com/blog/1322066

    关于wait和notify、notifyAll的运用例子:

        public static void main(String[] args) {
            waitAndNotifyAll();
        }
    
        private static Object object = new Object();
        private static void waitAndNotifyAll() {
            System.out.println("主线程启动");
            Thread thread = new WaitThread();
            thread.start();
            long startTime = System.currentTimeMillis();
            try {
                synchronized (object) {
                    System.out.println("主线程等待");
                    object.wait();
                }
            } catch (Exception e) {
                // TODO: handle exception
            }
            long time = System.currentTimeMillis() - startTime;
            System.out.println("主线程继续->等待耗时:" + time + " ms");
        }
    
        static class WaitThread extends Thread {
            @Override
            public void run() {
                // TODO Auto-generated method stub
                try {
                    synchronized (object) {
                        Thread.sleep(3000);
                        object.notifyAll();
                    }
                } catch (Exception e) {
                    // TODO: handle exception
                }
            }
        }
    
    

    运行后的结果

    主线程启动
    主线程等待
    主线程继续->等待耗时:3000 ms

    与多线程相关的方法--Callable、Future和FutureTask

    Runnable和Callable功能大致类似,不同的是Callable是一个泛型接口,切有一个返回值为Call()函数,而Runnable的run()函数不能讲结果返回给客户程序。Callable的声明:

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

    ==可返回值的任务必须实现Callable接口,类似的,无返回值的任务必须Runnable接口==

    Future提供了对Runnable或Callable任务的执行结果进行取消,查询是否完成,获取结果,设置结果操作,分别对应cancel,isDone,get,set函数。get方法会阻塞,直到任务返回结果;
    说到底,Future只是定义了一些规范的接口,而FutureTask才是具体实现类,FutureTask实现了RunnableFuture<V>,而RunnableFuture实现了Runnable有实现了Future<V>这2个接口,所以FutureTask具备了他们的能力

    关于Runnable、Callable、FutureTask的运用例子:

    public static void main(String[] args) {
            try {
                futureWithRunnable();
                futureWithCallable();
                futureTask();
            } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        static ExecutorService mExecutor = Executors.newSingleThreadExecutor();
        
        private static void futureTask() throws InterruptedException,ExecutionException {
            // TODO Auto-generated method stub
            FutureTask<Integer> result = new FutureTask<Integer>(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    // TODO Auto-generated method stub
                    return fibc(20);
                }
            });
            mExecutor.submit(result);
            System.out.println("FutureTask:"+result.get());
        }
    
        private static void futureWithCallable() throws InterruptedException,ExecutionException  {
            // TODO Auto-generated method stub
            Future<Integer> result = mExecutor.submit(new Callable<Integer>() {
    
                @Override
                public Integer call() throws Exception {
                    // TODO Auto-generated method stub
                    return fibc(20);
                }
                
            });
            System.out.println("Callable:"+result.get());
        }
        /**
         * runnable无返回值,所以get()的值为null
         */
        private static void futureWithRunnable() throws InterruptedException,ExecutionException {
            //提交runnable,
            Future<?> result = mExecutor.submit(new Runnable() {
                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    fibc(20);
                }
            });
            System.out.println("runnable:"+result.get());
        }
        //效率低夏的 斐波那契数列
        private static int fibc(int i) {
            // TODO Auto-generated method stub
            if (i == 0) {
                return 0;
            }
            if (i == 1) {
                return 1;
            }
            return fibc(i-1) + fibc(i-2);
        }
    
    

    结果:

    runnable:null
    Callable:6765
    FutureTask:6765

    在上面的例子中,第一个结果为null是因为Runnable没有回调结果,所以get的值为null
    第二个Callable那个是通过Future的get函数得到结果,
    FutureTask则是一个RunnableFuture<V>,既实现了Runnable又实现了Future<V>,另外
    还可以包装Runnable(实际转成Callable)和Callable<V>,提交给ExecuteService来执行后也可以通过
    返回的Future对象的get函数得到执行结果,在线程体没有执行完成时,主线程一直阻塞等待,执行完则直接返回结果。

    线程池

    引子:当需要频繁创建多个线程进行耗时操作时,每次通过new Thread实现性能很差,缺乏统一管理,可能无限制新建线程导致线程之间的竞争,可能占用多系统资源导致死锁,缺乏定时执行,定期执行,线程中断等功能;

    这时线程池就派上用场了,Java提供了4种线程池,它能有效管理调度线程,避免资源消耗,优点:

    1,重用存在的线程,减少对象创建、销毁的开销;
    2,可有效控制最大并发线程数、提高系统资源的使用率,同事避免过多资源竞争,避免堵塞;
    3,提供定时执行、定期执行、单线程、并发数控制等功能。

    java中的线程池扩展:http://cuisuqiang.iteye.com/blog/2019372

    ==线程池都实现了ExecutorService接口,该接口定义了线程池需要实现的接口,如submit、execute、shutdown等==

    启动指定数量的线程(ThreadPoolExecutor)与定时执行任务(ScheduledThreadPoolExecutor)

    1,ThreadPoolExecutor 是线程池的实现之一,功能是启动指定数量的线程以及将任务添加到一个队列中,并且将任务分发给空闲的线程。
    2,ScheduledThreadPoolExecutor在我们需要定时执行一些任务的场景使用,通过Executors和newScheduledThreadPool函数就可方便地创建定时执行任务的线程池。

    扩展:
    ThreadPoolExecutor详解 http://blog.chinaunix.net/uid-20577907-id-3519578.html
    Java线程池使用说明 http://www.oschina.net/question/565065_86540
    JAVA线程池的分析和使用 http://www.infoq.com/cn/articles/java-threadPool/
    Java 理论与实践: 线程池与工作队列 http://www.ibm.com/developerworks/cn/java/j-jtp0730/index.html

    AysncTask的原理

    AysncTask是解决Thread和Handler更新UI时的代码臃肿,多任务无法精确控制等缺点而产生的,它的诞生使得创建异步任务变得更加简单,不在需要编写任务线程和Handler实例,相对Handler和Thread来说易于使用
    ==使用注意:==

    • 异步任务的实例必须在UI线程中创建
    • execute(Params... params)方法必须在UI线程中调用
    • 不能在doInBackground(Params... params)中更改UI组件的信息
    • 一个任务实例只能执行一次,如果执行第二次将会抛出异常
    • 不要在程序中直接调用 onPreExecute()、onPostExecute()、doInBackgroud 和 onProgressUpdate 方法;

    AysncTask的执行原理

    1. doInBackground(Params... params) 是一个抽象方法,继承AsyncTask必须覆写此方法。
    2. onPreExecute()、onProgressUpdate(Progress... values)、onPostExecute(Result result)、onCancelled()这几个方法体都是空的,需要的时候可以选择性地覆写它们。
    3. publishProgress(Progress... values) 是final修饰的,不能覆写,只能调用,一般都会在doInBackground(Params... params)中调用此方法来更新进度条;

    实现一个简单的AsyncTask

    HandlerThread是自带消息队列的Thread类型,当线程

    public abstract class SimpleAsyncTask<Result> {
        private static final HandlerThread ht = new HandlerThread("SimpleAsyncTask", Process.THREAD_PRIORITY_BACKGROUND);
        static{
            ht.start();
        }
    
        final Handler mUIHandler = new Handler(Looper.getMainLooper());
        final Handler mAsyncHandler = new Handler(ht.getLooper());
    
        protected void onPreExecute(){}
        protected void onPostExecute(Result result){}
        protected abstract Result doInBackground();
        public final SimpleAsyncTask<Result> execute(){
            onPreExecute();
            mAsyncHandler.post(new Runnable() {
                @Override
                public void run() {
                    postResult(doInBackground());
                }
            });
            return this;
        }
    
        private void postResult(final Result result){
            mUIHandler.post(new Runnable() {
                @Override
                public void run() {
                    onPostExecute(result);
                }
            });
        }
    }
    
    

    总结:
    多线程编程在应用开发中随处可见,网络请求、IO操作等耗时操作都需要异步执行,线程池是进行异步操作的重要方式,在概念上十分简单,并且封装良好,基本满足需求。自行实现一个行为正确的线程池并不是那么容易,需要解决死锁、资源不足、和wait()以及notify()等复杂问题。
    因此建议在Executor类族的基础上正确的运用而不建议自定义线程池。

    参考书目:《Android从小工到专家》,《Android开发艺术探索》

    相关文章

      网友评论

        本文标题:Android多线程笔记

        本文链接:https://www.haomeiwen.com/subject/lzjnettx.html