Android中的多线程“安全”

发表于:2018-4-18 10:26

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:baiyang    来源:搜狗测试

  一、前言
  小编最近在进行车机项目测试,遇到了一些多线程操作引起的问题。针对发现的问题,进一步学习了相关知识。今天,小编和大家聊一聊:Android中的线程和“多线程”安全。
  二、线程安全
  1、概念介绍
      线程安全是编程中的术语,指某个函数、函数库在多线程环境中被调用时,能够正确地处理多个线程之间的共享变量,使程序功能正确完成,不会出现数据不一致或者数据污染。
      线程不安全是指不提供数据访问保护,多线程先后更改数据会产生数据不一致或者数据污染的情况。
      一般使用synchronized关键字加锁同步控制,来解决线程不安全问题。
  2、线程安全的集合对象
  ArrayList线程不安全,Vector线程安全;
  HashMap线程不安全,HashTable线程安全;
  StringBuilder线程不安全,StringBuffer线程安全。
  三、多线程操作问题实例
  1、问题实例
  问题定位:
  java.lang.ArrayIndexOutOfBoundsException:length=3; index=3
  数组索引越界异常,length为3,索引为3(索引从0开始);由于多线程操作,然后导致获取列表指定位置数据的时候,定位错误。
  ArrayList是线程不安全的,通过Collections.synchronizedList()方法可以将线程不安全的List转成线程安全的List。
  问题定位:调用list.remove()方法导致modCount和expectedModCount的值不一致。
  ArrayList的父类AbstarctList中有一个域modCount,每次对集合进行修改(增添元素,删除元素)时都会modCount++。如果modCount不等于expectedModCount,抛出ConcurrentModificationException异常。对Vector、ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常。
  Vector是线程安全的,但是有缺陷,原因在于,虽然Vector的方法采用了synchronized进行了同步,但是实际上通过Iterator访问的情况下,每个线程里面返回的是不同的iterator,也就是说expectedModCount是每个线程私有。在迭代的情况下,需对整个迭代过程加锁。
  2、总结
  如何取舍
  线程安全必须要使用synchronized关键字来同步控制,所以会导致性能的降低;当不需要线程安全时,可以选择ArrayList,避免方法同步产生的开销;当多个线程操作同一个对象时,可以选择线程安全的Vector。
  线程不安全!=不安全
  线程不安全并不是多线程环境下就不能使用。注意线程不安全条件:多线程操作同一个对象,比如在多个线程操作同一个ArrayList对象。如果每个线程中new一个ArrayList,而这个ArrayList只在这一个线程中使用,是没问题的。
  四、Android应用程序的单UI线程模型
  虽然Android支持多线程编程,但只有UI线程也就是主线程才可以操作控件,如果在非UI线程中直接操作UI控件,会抛出如下异常:
  android.view.ViewRoot$CalledFromWrongThreadException:Only the original thread that created a view hierarchy can touch its views。这是因为UI操作不是线程安全的,如果允许同时操作UI控件,可能会发生灾难性结果。
  有时候后台线程需要更新控件显示信息,例如一个后台下载的程序在完成下载后,需要更新UI提示用户。Android采用消息队列的机制来满足这个要求,如下图
  Android的单UI线程模型
  在消息队列机制中,不管是硬件(如触摸屏)还是后台线程,都可以向消息队列中放入UI消息,UI线程循环处理消息队列的消息,按消息的语义更新控件状态。
  由于UI线程负责事件的监听和绘图,因此,必须保证UI线程能够随时响应用户的需求,UI线程中的操作应该向终端事件那样短小,费时的操作(如网络连接)需要另开线程,否则,如果UI线程超过5s没有响应,会弹出对话框提醒用户终止应用程序。一般界面UI框架向来是单线程为主,常见的程序架构是一个UI线程加若干个非UI线程/进程。
  五、Android中的多线程
  Android沿用了JAVA的线程模型,其中的线程分为主线程和子线程,其中主线程又叫UI线程。在Android系统中,在默认情况下,一个应用程序内的各个组件(如Activity、BroadcastReceiver、Service)都会在同一个进程(Process)里执行,且由此进程的主线程负责执行。如果有特别指定(通过android:process),也可以让特定组件在不同的进程中运行。无论组件在哪一个进程中运行,默认情况下,他们都由此进程的主线程负责执行。主线程既要处理Activity组件的UI事件,又要处理Service后台服务工作,通常会忙不过来。为了解决此问题,主线程可以创建多个子线程来处理后台服务工作,而本身专心处理UI画面的事件。子线程的任务则是执行耗时任务,比如网络请求,I/O操作等。
  Android中的子线程
  Android中开启一个子线程,有以下两种方法:第一种是类继承Thread重写run方法,第二种是实习Runnable接口并重写run方法。
  1:继承Thread类
  public class MyThread extendsThread { 
     public void run(){ 
     } 
  }
  newMyThread().start();
  2:实现Runnable接口
  public class MyRunnable implementsRunnable{
  @Override
  public void run(){
     } 
  }
  new MyThread().start();
  Android提供了好几种方法让其他线程操作UI控件:
  Activity.runOnUiThread(Runnable)、View.post(Runnable)、Handler。
  这些类或方法代码比较复杂,因此Android提供了一个名为AsyncTask的辅助类来简化多线程代码。
  AsyncTask
  AsyncTask是一个轻量级的异步任务类,它可以在线程池中执行后台任务,然后把执行的进度和结果传递给主线程并且在主线程中更新UI。AsyncTask适用于简单的异步处理,不需要借助线程和Handler即可实现。
  AsyncTask是抽象类,定义了三种泛型类型Params,Progress和Result。
  (1).Params启动任务执行的输入参数,比如HTTP请求的URL。
  (2).Progress后台任务执行的百分比。
  (3).Result后台执行任务最终返回的结果,比如String。
  AsyncTask后台线程与UI线程的交互如下图:
  AsyncTask后台线程与UI线程的交互
  (1).onPreExecute(),该方法将在执行实际的后台操作前被UI thread调用。
  (2).doInBackground(Params...),将在onPreExecute方法执行后马上执行,该方法运行在后台线程中,这里将主要负责执行那些很耗时的后台计算工作。
  (3).onProgressUpdate(Progress...),在publishProgress方法被调用后,UI thread将调用这个方法从而在界面上展示任务的进展情况。
  (4).onPostExecute(Result),在doInBackground执行完成后,onPostExecute 方法将被UI thread调用,后台的计算结果将通过该方法传递到UI thread。
  以上是小编在测试过程中遇到的问题,并对问题原因进行了了解。希望对之后的精准测试和问题定位能力得到提升。欢迎小伙伴留言探讨,共同进步!



上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号