线程基础
可以把线程想象成单一顺序的指令集。这些指令集 被翻译并交给设备的硬件来执行。
多个指令集被同事执行时,这样的环境被称为多线程。多线程对提升任何系统的运行速度都有帮助。因为并行总是快于串行。此外对于有用户接口的程序,不仅可以提高程序的相应速度,通常也能更好的资源和系统管理。
多核cpu
知道几年前,处理器同一时刻只能执行一个命令。尽管多线程的框架已经存在。多线程中的代码使用Cpu时间片的技术顺序执行。所以并不是真正的多线程。这种情况下,我们并不清楚虚拟机会按照怎么航的顺序执行多线程的代码。然而多核cpu技术存在多年,可以同事执行多个代码,实现真正的多线程。
线程
要创建一个线程,需要使用Thread对象,调用Thread.start() 方法来启动它,让他与当前线程并发执行。于是虚拟机受到指令,就会创建一个新线程,并执行Thread.run() 方法中相关的字节码。将代码添加到线程中执行有两种方式:
- 继承自Thread类:创建一个继承自Thread的类,接着,你需要覆盖Thread.run() 方法,指定当前Thread.start() 方法被调用时要执行的内容。
- 实现Runable 接口:通过这种方式,当Thead.start() 方法被调用的时候,Runnable.run()方法中的代码就会执行。
一个线程总是由另外一个线程启动,所以总有一个特殊的线程,是第一个启动的线程。这个应用程序一启动就创建并执行的第一个线程,叫做主线程。
java为线程提供了一个优先级系统,这意味着可以改变某个线程的优先级,让他相对于其他线程更快或者更慢的执行。
和优先级有关的属性如下:
/**
* 线程的优先级属性
*/
private int priority;
/**
* 线程所能拥有的最大优先级.
*/
public final static int MIN_PRIORITY = 1;
/**
* 线程默认的优先级.
*/
public final static int NORM_PRIORITY = 5;
/**
* 线程所能拥有的最大优先级.
*/
public final static int MAX_PRIORITY = 10;
多线程特点:
多线程在应用程序汇总的执行顺序是不可预期的。不能保证那个线程被优先执行,或那个会先执行完成。而且这里所指的不仅是某块代码,甚至精确到某行代码。在一些特殊情况下,当需要按照某个特定顺序访问某个对象时,多线程的执行结果令人担忧。因此要让对象按照正确的顺序被访问,且同一时间内只能被同一个线程访问。换句话说,当一个对象被另外线程访问时候,要阻止其他线程对它进行访问。
线程安全
线程安全指的是代码的安全执行,共享的数据,不能并发的被修改。显而易见,访问一个只读对象,永远不会有安全问题。但可写对象,就可能存在问题。如果一个多线程应用程序在共享对象上没有并发操作,那么他就是线程安全的。
让我们看看这在java框架中意味着什么。java使用一个名为监视器(monitor)的概念。每个对象都有一个监视器。每个对象都有一个监视器。监视器确保同一个时刻一个对象只会被锁住一次。其他任何的锁定意图都会被放进队列中。
java通过一个关键字做这些和锁定相关的事情。Synchronized。 该关键字可以:
- 作为一个声明使用
- 也可以声明一个Synchronized方法
当两个或者更多的线程出现相互等待的情况,那么这些线程就永远被锁住了。
Android中的线程类型
有三类:
- 对应java中的主线程,Android中的主线程,又叫UI线程
- 从主线程启动新线程进行后台操作,为应用程序创建的其他线程,都被称作:后台线程(Background Thread)或者Worker Thread
- Binder线程,用于进程间通信
1) 主线程
主线程是唯一一个可以管理用户界面的线程,明白这点很关键。这也是为什么被称为UI线程。UI线程的生命周期和应用程序一样长,和程序进程一样长。因为应用程序需要这样一个线程,以保证应用程序在任何时候都能与应用程序进行交互。Android的UI不是线程安全的,如果View能被不同的线程所访问或修改,想象画面就很美——完全乱了套了。
另外也是为了加快UI,因为加锁和解锁操作消耗太大。
2)工作线程
Android的UI线程,可以处理一些非UI的操作,但金陵不要这么做。应该让主线程只处理UI相关的操作,任何耗时操作都必须放在非UI线程中完成。以保证应用程序的流畅度,带来良好的用户体验,者也是工作线程的主要目的。工作线程用来执行那些坑内会影响UI的耗时操作。更糟糕的是,如果在UI线程中进行这些操作,很可能会卡主UI,知道执行结束。这会引发我们熟悉的ANR异常。
Android中的线程框架
1)handler
(未完成,等待补充)
2) HandlerThread
他是一个非常有用的Thead子类,糅合了Thread和Handler。
1>与传统线程不一样,HandlerThread是可以被 重用的
他一直处于活跃状态,直到HandlerThread.quit()方法被调用。这个特殊的方法能终止looper,任何之后发送的Message或者Runnable都会失败,并且MessageQueue会被清空。该方法会强制等待中的Message和Runnable退出,他们不会被分发到Handler。为了确保没有等待中的消磁会被终止或分发,可以使用HandlerThread.quitSafely()方法。但是无论哪个方法被调用,HandlerThread对象都不能再被使用,就像Thread已经执行结束了一样
2> 当我们需要一个可供一直使用的线程时,HandlerThread是一个不错的选择。
3> 当不在使用时候,记得退出以释放资源
处理多线程和线程间通信时,使用HandlerThread对Looper进行管理,是一个不错的选择。他还可以被多个Message和Runnable重用。但要记住,当不在使用时候,记得退出以释放资源
3)AsyncTask
内部含有四个需要重载的方法,doInBackGround()方法是抽象的,并且执行在工作线程中。
- onPreExecute() 在启动后台线程之前被调用,可用于告知用户:“后台在处理一些事情”
- onProgressUpdate() 当工作线程中的进度有变化时,可在此方法中对UI进行更新
- onPostExecute() 该方法用于处理从工作线程返回的结果
- onCancelled() 当AsyncTask在UI线程中被取消的时,可通过该方法处理一些事务。
(未完成,有待添加)
使用技巧总结
- AsyncTask的目的是让工作线程能与UI线程通信。那么,如果我们的后台操作不需要通知用户,或者说,不需要更新UI,也就没必要使用AsyncTask。一个Thread就够了,并且性能比Thread好。
- AsyncTask在Activity以内部类的形式多次使用,存在内存泄露的风险。
4) Loader
Loader能对AsyncTask进行有效的替换,并弥补了他的部分不足。
loader非常适合异步操作,比如,从远程服务器检索数据。当新数据可用时,他们能触发回调,通知调用者。调用者肯能是Activity或者Fragment. Loader是声明周期独立的,他与Activity的Fragment的生命周期变化无关系。他仍然在后台进行操作。并且会通知最新创建的Activity和Fragment实例。中间还存在缓存机制,可以放心使用。
Loader使用的应用上下文,无Activity泄漏风险
LoaderManager
- 每个Activity或Fragment有且只有一个LoaderManager.
- 在Activity和Fragment中,通过getLoaderManager()方法获得
- LoaderManger能对loader进行操作
使用技巧
在使用loader的时候我们通常要使用:
- CursorLoader或者AsyncTaskLoader这样的包装过的类
- 或者创建一个Loader的子类
总结:
- 无泄漏风险
- 为用户缓存数据
Loader框架改进了AsyncTask的特性,让用户不用担心Activity或者Fragment的生命周期,并且为用户缓存数据。因此它是AsyncTask的一个有效替代品。 - 使用更简单方便
使用和管理Loader 更加容易。
5)Service
- Service没有UI要处理
- 他的主要目的,是在后台执行长时间的操作
想想我们前面看到的各种 线程,在大部分情况下,使用它们是为了Activity 的生命周期内对UI更新。但Service不同,他是一个不受任何限制,独立运行在后台的对象。 - service 不需要与用户交互,也不需要用户界面。
- 因此大部分不需要和用户交互的操作,都可以放到Service中进行。
有两种类型的Service.
- Started Service
- Bound Service
(未完成,后面补充)
6)IntentService
IntentSevice 是平台提供的一个队Service的特殊实现。他在某些情况下非常有用。IntentService包装了一个单独的后台线程,来执行不同的请求,这些请求与Intent有关,传入的Intent 被放置在一个队列中,依次执行。当队列变空的时,IntentService会自动销毁。因此,他的生命周期与Service不同。只有当后台线程还在执行操作的时,它才是活跃的状态。
使用技巧
- 当你需要在一个单独线程中,顺序的执行一系列后台操作的时,IntentService是一个正确的选择
- 你不需要处理Service的生命周期(他提供了异步操作所需的一切,并且不会影响UI)
二 进程间通信
1) 远程过程调用(Rpc)
2) AIDL技术
3)Messager
4) ContentProvider异步技术
5)BroadcastReceiver异步技术
它有一个异常短暂的生命周期。所以并不能执行长时间运行的操作。然而broadcastReceiver非常适合于启动一个后台线程,例如IntentService 。
三. 重复性任务
Timer
ScheduledExecutorService
AlarmManager
7)多线程调试
StrictMode 等
参考文献:
《Android 高性能变编程》【西班牙】Enrique López Mañas(恩里克·洛佩斯·马尼亚斯),【意】Diego Grancini (迪戈·格兰奇尼)著叶坤 译
《Kotlin实战》【俄】Dmitry Jemerov Svetlana Isakova 著 覃宇 罗丽 李思阳 蒋扬海 译
《重构改善既有代码设计》【美】马丁福勒(MartinFowler)著 熊节 林从羽译
Android app性能调优