开启多线程的姿势

多线程

作用:将耗时的任务放到子线程去执行,以保证UI线程的流畅性

  子线程开启后,如果没有及时关闭,即使退出程序,子线程也一直会在后台运行,除非在应用管理强制关掉进程,才能完全关闭子线程。


如何开启一个子线程
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                    }
                }
        ).start();
        new Thread(){
            @Override
            public void run() {
                super.run();
            }
        }.start();

两者之间的区别

在java中可有两种方式实现多线程,一种是继承Thread类,一种是实现Runnable接口;Thread类是在java.lang包中定义的。一个类只要继承了Thread类同时覆写了本类中的run()方法就可以实现多线程操作了,但是一个类只能继承一个父类,这是此方法的局限。

在实际开发中一个多线程的操作很少使用Thread类,而是通过Runnable接口完成。因为Runnable可以实现资源共享。

比如说使用Thread继承的方法:

package org.demo.dff;  
class MyThread extends Thread{  

private int ticket=10;  

public void run(){  

for(int i=0;i<20;i++){  
if(this.ticket>0){  
System.out.println("卖票:ticket"+this.ticket--);  

}  
}  
}  
}; 
MyThread mt1=new MyThread();  
MyThread mt2=new MyThread();  
MyThread mt3=new MyThread();  
mt1.start();//每个线程都各卖了10张,共卖了30张票  
mt2.start();//但实际只有10张票,每个线程都卖自己的票  
mt3.start();//没有达到资源共享  

如果用Runnable就可以实现资源共享,下面看例子:

package org.demo.runnable; 

class MyThread implements Runnable{ 

private int ticket=10; 

public void run(){ 

for(int i=0;i<20;i++){ 
if(this.ticket>0){ 
System.out.println("卖票:ticket"+this.ticket--); 

} 
} 
} 
} 

package org.demo.runnable; 

public class RunnableTicket { 
public static void main(String[] args) { 
MyThread mt=new MyThread(); 
new Thread(mt).start();//同一个mt,但是在Thread中就不可以,如果用同一 
new Thread(mt).start();//个实例化对象mt,就会出现异常 
new Thread(mt).start(); 
} 
}; 

虽然现在程序中有三个线程,但是一共卖了10张票,也就是说使用Runnable实现多线程可以达到资源共享目的。


经典的模拟火车卖票程序

接下来使用经典的模拟火车卖票程序,来理解Thread和Runnable在特定场景下的区别和联系,以及synchronized在线程中的作用:

class AutoSaleTicket implements Runnable {  
    private int ticket = 20;  

    public void run() {  

        while (true) {// 循环是指线程不停的去卖票  
            // 当操作的是共享数据时,用同步代码块进行包围起来,这样在执行时,只能有一个线程执行同步代码块里面的内容  
            synchronized (this) {  
                if (ticket > 0) {  

                    // 不要在同步代码块里面sleep,作用只是自已不执行,也不让线程执行  
                    System.out.println(Thread.currentThread().getName()  
                            + " 卖出 第 " + (20 - ticket + 1) + " 张票");  
                    ticket--;  

                } else {  
                    break;  
                }  
            }  
            // 所以把sleep放到同步代码块的外面,这样卖完一张票就休息一会,让其他线程再卖,这样所有的线程都可以卖票  
            try {  
                Thread.sleep(200);  
            } catch (Exception ex) {  
            }  
        }  
    }  

我们开始执行售票程序:

AutoSaleTicket ticket = new AutoSaleTicket();  
Thread t1 = new Thread(ticket, "东城代售");  
Thread t2 = new Thread(ticket, "西城代售");  
Thread t3 = new Thread(ticket, "朝阳代售");  
Thread t4 = new Thread(ticket, "海淀代售");  
t1.start();  
t2.start();  
t3.start();  
t4.start();  

结果:

这里写图片描述


Runnable 并不一定是新开一个线程,比如下面的调用方法就是运行在UI主线程中的:

     Handler mHandler=new Handler(); 
     mHandler.post(new Runnable(){ 
        @Override public void run() 
        { // TODO Auto-generated method stub 
         } 
     });

我们可以通过调用handler的post方法,把Runnable对象(一般是Runnable的子类)传过去;handler会在looper中调用这个Runnable的Run方法执行。

Runnable是一个接口,不是一个线程,一般线程会实现Runnable。所以如果我们使用匿名内部类是运行在UI主线程的,如果我们使用实现这个Runnable接口的线程类,则是运行在对应线程的。

具体来说,这个函数的工作原理如下:
View.post(Runnable)方法。在post(Runnable action)方法里,View获得当前线程(即UI线程)的Handler,然后将action对象post到Handler里。在Handler里,它将传递过来的action对象包装成一个Message(Message的callback为action),然后将其投入UI线程的消息循环中。在Handler再次处理该Message时,有一条分支(未解释的那条)就是为它所设,直接调用runnable的run方法。而此时,已经路由到UI线程里,因此,我们可以毫无顾虑的来更新UI。

如下图,前面看到的代码,我们这里Message的callback为一个Runnable的匿名内部类

这种情况下,由于不是在新的线程中使用,所以千万别做复杂的计算逻辑。

这里写图片描述


  mHandler=new Handler();
  mHandler.post(new Runnable(){
  void run(){
  //执行代码...
  }
  });
  这个线程其实是在UI线程之内运行的,并没有新建线程。
  常见的新建线程的方法是:
  Thread thread = new Thread();
  thread.start();
  HandlerThread thread = new HandlerThread("string");
  thread.start();

使用 Java Runnable接口来实现多线程使得我们能够在一个类中包容所有的代码,有利于封装,它的缺点在于,我们只能使用一套代码,若想创建多个线程并使各个线程执行不同的代码,则仍必须额外创建类,如果这样的话,在大多数情况下也许还不如直接用多个类分别继承 Thread 来得紧凑。


子线程如何与主线程通信

1、Handler:子线程调用Handler的handle.sendMessage(msg);

    class MyThread extends Thread {
        @Override
        public void run() {
            Message message = new Message();
            handler.sendMessage(message);
            super.run();
        }
    }

    Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            textView.setText("接收消息");
        }
    };

2、Activity.runOnUiThread(Runnable)

 new Thread(new Runnable() {

            @Override
            public void run() {
                // 耗时操作            
                loadNetWork();
                MainActivity.this.runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textView.setText(来自网络的文字);
                    }
                });

            }
        }).start();

源码

    /** 在UI线程里面执行指定的动作. 如果当前线程是UI线程,则立刻执行该动作,
     * 如果当前线程不是UI线程,则将该动作发送到动作序列中,等UI线程为当前线程时再执行。
     * @action 传递的参数为action
     * @param action the action to run on the UI thread
     */
    public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }

把更新ui的代码创建在Runnable中,然后在需要更新ui时,把这个Runnable对象传给runOnUiThread(Runnable)。 这样Runnable对像就能在ui程序中被调用。如果当前线程是UI线程,那么行动是立即执行。如果当前线程不是UI线程,操作是发布到事件队列的UI线程。
其实和handler差不多,都是将这个更新UI的请求消息,加入到事件队列,等待主线程空闲的时候执行。

3、View.post(Runnable)

        new Thread(new Runnable() {

            @Override
            public void run() {
                // 耗时操作           
                loadNetWork();
                textView.post(new Runnable() {

                    @Override
                    public void run() {
                        textView.setText(来自网络的文字);
                    }
                });

            }
        }).start();

源码

/**
     * <p>Causes the Runnable to be added to the message queue.
     * The runnable will be run on the user interface thread.</p>
     * 将Runnable被添加到消息队列中。runnable将在用户界面线程上运行。
     *
     * @param action Runnable that will be executed.
     *               参数 action :  Runnable 将会被执行.
     *               
     * @return Returns true if the Runnable was successfully placed in to the
     * message queue.  Returns false on failure, usually because the
     * looper processing the message queue is exiting.
     * 返回 true :Runnable已成功放置到消息队列中
     * 返回 false : 失败时返回false,通常是looper处理消息队列时正在退出
     * 
     * @see #postDelayed
     * @see #removeCallbacks
     */
    public boolean post(Runnable action) {
        final AttachInfo attachInfo = mAttachInfo;
        if (attachInfo != null) {
            return attachInfo.mHandler.post(action);
        }

        // Postpone the runnable until we know on which thread it needs to run.
        // 推迟runnable,直到我们知道哪个线程需要运行
        // Assume that the runnable will be successfully placed after attach.
        // 假设runnable将在附加后成功放置。
        getRunQueue().post(action);
        return true;
    }

4、View.postDelayed(Runnable,long)

        new Thread(new Runnable() {

            @Override
            public void run() {
                // 耗时操作           
                loadNetWork();
                textView.postDelayed(new Runnable() {

                    @Override
                    public void run() {
                        textView.setText(来自网络的文字);
                    }
                }, 10);

            }
        });

5、AsyncTask

    private class MyAsycnTask extends AsyncTask {

        //后台线程执行时 
        @Override
        protected Object doInBackground(Object... params) {
            // 耗时操作             
            return loadNetWork();
        }

        //后台线程执行结束后的操作,其中参数result为doInBackground返回的结果 
        @Override
        protected void onPostExecute(Object result) {
            super.onPostExecute(result);
            textView.setText(result);
        }

    }

以上5个方法注意:

  • 这些方法或者类必须在在UI线程中创建和调用

  • 其实这些方法和类最终的实现都是Android的Message、MessageQueue和Looper的机制,所以不要期待你会马上看到结果(效果),因为这是一个Loop一直循环出MessageQueue中的Message执行的过程,如果你没有看到效果,那么等等吧,因为还没有轮到你。

  • 有线程(多个)的地方就会有并发,会有资源共享冲突,所以在使用的时候谨慎点吧,说不准你的一个线程中使用的变量已经被另一个线程改的面目全非了


Thread开启和结束的陷阱
new Thread(

new Runnable() {
@Override
public void run()
{ }
}
)
.start();
  • 这种方式仅仅是起动了一个新的线程,没有任务的概念,不能做状态的管理。start之后,run当中的代码就一定会执行到底,无法中途取消。

  • Runnable作为匿名内部类还持有了外部类的引用,在线程退出之前,该引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。

  • 没有线程切换的接口,要传递处理结果到UI线程的话,需要写额外的线程切换代码。

  • 如果从UI线程启动,则该线程优先级默认为Default,归于default cgroup,会平等的和UI线程争夺CPU资源。这一点尤其需要注意,在对UI性能要求高的场景下要记得


总结

Android中的线程延续了JAVA的设计模型,默认一个应用程序只有一个主线程,主线程的开启是在Activity的main()方法。主线程实际上是一个死循环,不断的循环处理系统以及其他子线程发来的消息。主线程的绑定是在DecorView初始化的时候,也就是生命周期的onResume()之后。主线程主要处理UI操作,和Broadcast相关消息,主线程如果长时间无法响应,将出现ANR,为了避免ANR,耗时操作一般都开启子线程处理。子线程处理完再发消息通知主线程来改变UI


AsyncTask

AsyncTask.cancel()的结束问题:

  1. 值得注意的是AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。cancel()是给AsyncTask设置一个”canceled”的状态,那么想要终止异步任务,就需要在异步任务当中结束。

  2. 我们可以随时调用 cancel(boolean)去取消这个加载任务,调用这个方法会间接调用 iscancelled 并且返回true。

  3. 当调用cancel()后,在doInBackground()return后 我们将会调用onCancelled(Object) 不在调用onPostExecute(Object)。

  4. 为了保证任务更快取消掉,你应该在doInBackground()周期性的检查iscancelled 去进行判断。

  5. 我们的oncancel和onPostExecute一样,都是在UI线程中执行……所以当我们想要取消之后,有些界面变化我们可以在oncancel里面改变UI。

想要终止异步任务,就需要在异步任务当中结束:


@Override
public void onProgressUpdate(Integer... value) {
    // 判断是否被取消
    if (isCancelled())
    return;
    .........
        }


        @Override
protected Integer doInBackground(Void... mgs) {

    // Task被取消了,马上退出
    return null;
    .......

    // Task被取消了,马上退出
    if (isCancelled()) 
    return null;
        }
    .......

结束异步任务的条件:

if(loadAsyncVedio!=null&&!loadAsyncVedio.isCancelled()
                &&loadAsyncVedio.getStatus()==AsyncTask.Status.RUNNING)

        {
            loadAsyncVedio.cancel(true);
            loadAsyncVedio = null;
        }

AsyncTask线程优先级为background,对UI线程的执行影响极小。


HandlerThread
  • 在需要对多任务做更精细控制,线程切换更频繁的场景之下,Thread()和AsyncTask都会显得力不从心。HandlerThread却能胜任这些需求甚至更多。

  • HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。

  • HandlerThread背后只有一个线程,所以任务是串行执行的。串行相对于并行来说更安全,各任务之间不会存在多线程安全问题。

  • HandlerThread所产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。这一点和Thread(),AsyncTask都不同,thread实例的重用可以避免线程相关的对象的频繁重建和销毁。

  • HandlerThread较之Thread(),AsyncTask需要写更多的代码,但在实用性,灵活度,安全性上都有更好的表现。


ThreadPoolExecutor

Thread(),AsyncTask适合处理单个任务的场景,HandlerThread适合串行处理多任务的场景。当需要并行的处理多任务之时,ThreadPoolExecutor是更好的选择。

public static Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);

线程池可以避免线程的频繁创建和销毁,显然性能更好,但线程池并发的特性往往也是疑难杂症的源头,是代码降级和失控的开始。多线程并行导致的bug往往是偶现的,不方便调试,一旦出现就会耗掉大量的开发精力。

ThreadPool较之HandlerThread在处理多任务上有更高的灵活性,但也带来了更大的复杂度和不确定性。


IntentService

不得不说Android在API设计上粒度很细,同一样工作可以通过各种不同的类来完成。IntentService又是另一种开工作线程的方式,从名字就可以看出这个工作线程会带有service的属性。和AsyncTask不同,没有和UI线程的交互,也不像HandlerThread的工作线程会一直存活。IntentService背后其实也有一个HandlerThread来串行的处理Message Queue,从IntentService的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);
    }

只不过在所有的Message处理完毕之后,工作线程会自动结束。所以可以把IntentService看做是Service和HandlerThread的结合体,适合需要在工作线程处理UI无关任务的场景。


参考文章

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值