一、前言
二、线程安全
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),我们将立即处理。