多线程开发(上)

更新时间修改意见
2016-08-02陈敏

第1节 概述

安卓应用只有一个主线程-各个组件都是在这个线程中运行。作为组件的之一的Activity就是在这个线程中更新应用界面的,例如,用户点击界面上的一个按钮,按钮得到响应,整个过程就是在这个主线程里。所以这个主线程绝对不可以做耗时的操作。假如在按钮中做了耗时的操作,那么当它进行耗时操作的时候,你去点击界面上的其它按钮是不会有反应的,就好像程序冻在了那里。

我们的代码一旦连续占用这个线程超过一定的时间,系统就会弹出“程序无响应的”提示,这个提示叫做ANR-Applicatin No Response。

这就好比你在正在做一件事情A,突然另一件事情B来打扰你,你不得不停下手头的工作来完成,做完了才能继续之前的工作;这时如果有另外一个人(另一个线程)来帮助你,把事情B全部包揽了,那你就不用分心了。当另一个人把事情B做完后,告诉你一声就可以了。

启动一个新的线程,分担耗时工作的方法是一种异步操作:我让你帮我做一件事情,布置任务后,我就去做其他的事情了,等你做完了再告诉我结果;

与它对应的是同步操作:我让你帮我做一件事情,布置任务后,我啥也不做,就等着你做完了告诉我结果;

例如,一个视频播放应用,获取视频信息所需要的时间是个不能确定的事情。如果视频很少,也许几十毫秒就能完成,如果视频很多(比如几十个),也许就要花二十多秒。

因此,我们可以考虑把获取视频信息的操作放到一个单独的线程thread中进行。启动一个新线程-工作线程thread-查询视频信息,查询完成后,工作线程再将结果通知到主线程,让主线程将查询到的结果显示到界面上。因为界面的更新一定要在主线程中进行,不能在别的线程修改,否则系统会提示运行错误。因此我们一定要将查询的结果发送给主线程,让主线程处理界面的更新。

这里就涉及到了线程的创建,工作分配,以及它们之间的配合-信息的传递。


/*******************************************************************/
* 版权声明
* 本教程只在CSDN安豆网发布,其他网站出现本教程均属侵权。
/*******************************************************************/

第2节 线程Thread

一个Activity运行在一个主线程上,它不能进行耗时巨大的操作,也不能执行耗时不确定的操作。因此需要在新的线程中进行这些耗时的操作,这种进行耗时操作的线程称作工作线程。

2.1 Thread的简单使用

创建一个新的线程,

  1. 创建一个Runnable,重写它的run()函数,这个函数里用来进行耗时的操作;

    
        @Override
        public void run() {
            //耗时的操作   
            while(xxx)
            {
    
            }
        }
    };
  2. Runnable为参数,创建一个Thread,调用Threadstart()方法后,新线程就运行起来了,并执行Runnable中的run()函数;

    Thread thread = new Thread(runnable);
    thread.start();
  3. run()函数执行完毕后,新线程就退出;

在线程执行耗时操作的过程中,有时要取消这个线程的操作,

  1. 最好的办法是在run()函数中增加一个标志位,让工作线程可以检查这个标志位是否被设置上,如果设置上了,就让run()函数,立即返回,

    //设置标志位,为退出线程使用
    boolean needStop = false;
    ......
    Runnable runnable = new Runnable() {
    
        @Override
        public void run() {
            //耗时的操作
            while(!needStop)
            {
    
            }  
        }
    };
  2. 如果主线程要取消这个线程的工作,修改这个标志位就好了,

    needStop = true;

2.2 Thread的创建

Thread可以拥有不同的优先级,从低到高有10级。操作系统根据线程的优先级来进行资源调度,优先为优先级高的线程分配CPU资源。在默认情况下,新创建的线程使用默认的优先级NORM_PRIORITY。设置优先级的方式很简单,

Thread thread = new Thread(runnable);
thread.setPriority(Thread.NORM_PRIORITY);

可以为线程取名字,当我们用Android Monitor工具调试应用的时候,就能看到创建线程时它的名字,方便我们观察、调试程序,

Thread thread = new Thread(runnable, "新线程的名字");

有时,一个应用会同时启动多个Thread,在创建它们的时候可以为它们设置ThreadGroup参数,将它们分成一组,便于对这些线程进行统一的管理。比如,中断这个组里所有线程的运行;

ThreadGroup group = new ThreadGroup("线程组");
Thread thread1 = new Thread(runnable, group);
thread1.start();
Thread thread2 = new Thread(runnable, group);
thread2.start();
Thread thread3 = new Thread(runnable, group);
thread3.start();

//中断3个线程的执行
group.interrupt();

2.3 Thread的停止

Thread的停止就是指这个线程的退出。线程的退出原因无外乎两种,

  1. 工作线程的工作完成了;
  2. 工作线程虽然正在进行耗时工作,但是被取消了,要提前结束;

2.3.1 正常退出

Runnable中的run()函数执行完并返回后,当前的Thread就退出了。

2.3.2 使用标志位

  1. run()函数中增加一个标志位,工作线程会随时检查这个标志位是否被设置上,如果设置上了,就让run()函数,立即返回,

    //设置标志位,为退出线程使用
    boolean needStop = false;
    ......
    Runnable runnable = new Runnable() {
    
        @Override
        public void run() {
            //耗时的操作
            while(!needStop)
            {
    
            }  
        }
    };
  2. 如果主线程要取消这个线程的工作,修改这个标志位就好了,

    needStop = true;

2.3.3 使用interrupt()方法

interrupt()是线程提供的一个标准的线程退出方法,如果当前的工作线程正被Object.waitThread.joinThread.sleep阻塞,那么使用thread.interrupt()之后,正在运行的线程会抛出一个InterruptedException异常,

Runnable runnable = new Runnable() {

    @Override
    public void run() {
        ...

        while(!needStop)
        {

            try {
                ......
                Sleep(1000); 

            } catch ( InterruptedException e ) {  
                needStop = true
            }

        }
    }
};

不过interrupt()方法并不是万能的,不是所有阻塞情况下都能够让线程立即退出。

例如当该线程正在用ServerSocketaccept()方法等待连接的时候,即使调用了这个工作线程的interrupt()方法,该线程还是不会抛出异常的。

它只对Object.waitThread.joinThread.sleep这几种阻塞有效果。对于网络读取数据时阻塞状态解除是没有效果的。

*对于网络读取数据时造成的阻塞,我们会在以后相应的章节详细介绍解决方法。

2.4 线程之间的同步

线程间的同步就是指线程A执行到一个地方的时候,停了下来,等待线程B的执行结果;等线程B执行出结果后,线程A才能继续执行。

2.4.1 join()方法

最常见的就是主线程的执行,依赖于工作线程的退出。

主线程启动了工作线程以后,有时候需要等到工作线程结束以后再进行接下来的操作。

例如一个Activity,在onCreate()的时候创建了一个工作线程Thread B;后来在用户的要求下,Activity退出,要被销毁了;销毁Activity时,主线程要等到Thread B执行完了才能继续接着进行剩下的清理工作,那么Activity可以在它的onDestroy()函数中可以使用join()方法,等待子线程的结束,

private Thread mTreadB;

@Override
protected void onCreate() {
    super.onCreate();

    mThreadB = new Thread(runnable);
    mThreadB.start();
}

@Override
protected void onDestroy() {
    super.onDestroy();

    mThreadB.join();
    //进行剩下的清理操作

join()方法,会一直处于阻塞状态,直到线程B退出。

onDestroy()中使用join()有天生的缺点:不能在主线程中进行耗时不可控的操作(例如这里等待工作线程执行完毕),万一工作线程的退出花费了很长的时间,那就有问题了。这里的场景只是用来举一个例子而已。

2.4.2 wait()方法

利用Objectwait()方法实现线程间的同步,需要线程之间共享一个“锁”-Object对象。

final Object lock = new Object();

当主线程A执行到一个阶段的时候,如果要等待线程B,就把锁置于等待状态,

synchronize(lock) {
    lock.wait()
}

此时,线程A就出于阻塞的状态,不能往下执行了。

线程B继续它的运行,当它执行到一个阶段的时候,将锁设置成放行状态,

synchronize(lock) {
    lock.notify();
}

此时,线程A的阻塞状态解除,可以继续往下执行了。


/*******************************************************************/
* 版权声明
* 本教程只在CSDN安豆网发布,其他网站出现本教程均属侵权。

*另外,我们还推出了Arduino智能硬件相关的教程,您可以在我们的网店跟我学Arduino编程中购买相关硬件。同时也感谢大家对我们这些码农的支持。

*最后再次感谢各位读者对安豆的支持,谢谢:)
/*******************************************************************/

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值