线程基础知识
线程状态
线程可以有如下6中状态
- New(新创建)
- Runable(可运行)
- Blocked(被阻塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(被终止)
可调用用getState()方法获取当前状态
新创建线程
比如new Thread(r),该线程还没有开始运行,意味着它的状态为new。
可运行线程
一旦调用start方法,线程处于runable状态,一个可运行的线程可能正在运行也可能没有运行,这取决与操作系统给线程提供运行的时间。一旦一个线程开始运行,它不必始终都在运行,事实上,运行过程中,它可能会被中断,而中断的目的就是让其他线程获得运行机会,线程调度的细节依赖于操作系统提供的服务。抢占式调度系统给每一个可运行线程一个时间片来执行任务,当时间片用完,操作系统剥夺该线程的运行权,并给另一个线程运行机会。
被阻塞线程和等待线程
当线程被阻塞或等待线程时,它暂时不活动。它不运行任何代码且消耗最少的资源,知道线程调度器激活它。细节取决于它是怎样达到非活动状态的。
- 当一个线程试图获取一个内部的对象锁,而该锁被其它线程所有,则该线程进入阻塞状态,当其它线程释放该锁,并且线程调度器允许本线程持有它的时候,该线程将变成非阻塞状态。
- 当线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。
- 调用Object.wait方法或Thread.join方法
- 等待java.util.concurrent库中的Lock或Condition时
- 有几个方法有一个超时参数。调用它们导致线程进入计时等待状态。这一状态将一直保持到超时期满或者接收到适当的通知。带有超时期参数的方法有Thread.sleep和Object.wait,Thread.join,Lock.tryLock以及Condition.await的计时版。
被终止的线程
线程因如下两个原因之一而被终止:
- 因为run方法正常退出而死亡
- 因为一个没有捕获的异常终止了run方法而意外死亡
可以调用线程的stop方法杀死线程,该方法抛出一个ThreadDeath错误对象,由此杀死对象,但是,stop方法已经过时,不要再自己的代码中调用这个方法。
线程属性
包括:线程优先级,守护线程,线程组以及处理未捕获异常的处理器
线程优先级
每一个线程有一个优先级。默认情况下,一个线程继承它的父线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级,范围是MIN_PRIORITY(1)与MAX_PRIORITY(10)之间的任何值。NORM_PRIORITY被定义为5.
每当线程调度器有机会选择新线程时,它首先会选择具有较高优先级的线程。
守护线程
可以通过t.setDaemon(true)将线程转换为守护线程,守护线程的唯一用途是为其它线程提供服务。计时线程就是一个例子,它定时地发送“计时器滴答”信号给其它线程或清空过时的高速缓存项的线程。当只剩下守护线程时,虚拟机就退出了,由于如果只剩下守护线程,就没必要继续运行程序了。
未捕获异常处理器
线程的run方法不能抛出任何被检测的异常,但是,不被检测的异常会导致线程终止,在这种情况下,线程就死亡了。
不需要任何catch字句就可以来处理可被传播的异常,相反,就在线程死亡之前,异常被传递到一个用于未捕获异常的处理器,该处理器必须属于一个实现Thread.UncaughtExceptionHandler接口的类,这个接口只要一个方法:void uncaughtException(Thread t,Throwable e),可以用setUncaughtExceptionHandler为任何线程安装一个处理器,也可以用Thread类的setDefaultUncaughtExceptionHandler()为所有线程安装一个默认的处理器。
如果不安装默认的处理器,默认的处理器为空
同步
在大多数多线程应用中,两个或两个以上的线程需要共享对同一数据的存取。根据各线程访问数据的次序,可能会产生讹误的对象,这种情况成为竞争条件。如果并发的每个线程对共享数据的操作源于块不是原子操作,即它实际上编译后是多个虚拟机指令,比如说增值命令是由几条指令组成的,那么执行它们的线程可以在任何一条指令点上被中断。
锁对象
有两种机制防止代码块并发访问的干扰。
- Java语言提供一个synchronized关键字,并且Java SE 5.0引入了ReentrantLock类。synchronized关键字自动提供一个锁以及相关的“条件”,对于大多数需要显示锁的情况很便利。
- java.util.concurrent框架
myLock.lock();//a ReentrantLock object
try{
critical section
}
finally{
myLock.unlock();//make sure the lock is unlocked even if an exception is thrown
}
这一结构确保任何时刻只有一个线程进入临界区,一旦一个线程封锁了对象,其它任何线程都无法通过lock语言。当其它线程调用lock时,他们被阻塞,知道第一个线程释放锁对象。
android 的消息机制
android的消息机制主要是指handler的运行机制,handler的运行需要底层的MessageQueue和Looper的支撑。MessageQueue是消息队列,内部储存了一组消息,以队列的形式对外提供插入和删除的工作,MessageQueue只是个消息的存储单元,它不能去处理消息,而Looper就可以以无限循环的形式去查找是否有新消息,如果有的话就处理,没有的话就一直等待着。Handler创建的时候会采用当前线程的Looper来构造消息循环系统。
具体介绍,这儿有一篇很好的文章,http://www.jianshu.com/p/d88e1af5100f
什么姿势开线程?
new thread()
new Thread(new Runnable() {
@Override
public void run() {
}
}).start();
优点:这种方式简单粗暴,是Android系统中开线程最简单的方式,也只能应用于最简单的场景,简单却伴随着不少的隐患。
缺点:
- 仅仅是启动了一个新的线程,没有任务的概念,不能做状态的管理
- Runnable作为匿名内部类还持有了外部类的引用,先线程退出之前,改引用会一直存在,阻碍外部类对象被GC回收,在一段时间内造成内存泄漏。
- 没有线程切换的接口,要传递处理结果到UI线程的话,需要些额外的线程切换代码。
ThreadPoolExecutor 线程池
优点:
- 重用线程池中的线程, 避免因为线程的创建和销毁所带来的性能开销.
- 有效控制线程池中的最大并发数,避免大量线程之间因为相互抢占系统资源而导致的阻塞现象.
- 能够对线程进行简单的管理,可提供定时执行和按照指定时间间隔循环执行等功能.
AsyncTask
public class MyAsyncTask extends AsyncTask {
@Override
protected Object doInBackground(Object[] params) {
return null;
}
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Object o) {
super.onPostExecute(o);
}
}
优点:
几处回调里可以有机会去中断任务,在任务状态的管理上较之Thread()方式更为灵活,AsyncTask的cancel()方法并不会终止任务的执行,开发者需要自己去检查cancel的状态值来决定是否中止任务。
线程优先级为background,对UI线程的执行影响极小。
缺点:AsyncTask也有隐式的持有外部类对象引用的问题,需要特别注意防止出现意外的内存泄漏。
AsyncTask由于在不同的系统版本上串行与并行的执行行为不一致,被不少开发者所诟病,这确实是硬伤,绝大部分的多线程场景都需要明确任务是串行还是并行。
HandlerThread
HandlerThread将Handler,Thread,Looper,MessageQueue几个概念相结合。Handler是线程对外的接口,所有新的message或者runnable都通过handler post到工作线程。Looper在MessageQueue取到新的任务就切换到工作线程去执行。不同的post方法可以让我们对任务做精细的控制,什么时候执行,执行的顺序都可以控制。HandlerThread最大的优势在于引入MessageQueue概念,可以进行多任务队列管理。
特点:
- HandlerThread背后只有一个线程,任务是串行执行的。
- HandlerThread产生的线程会一直存活,Looper会在该线程中持续的检查MessageQueue。
- 较之Thread,AsyncTask需要写更多的代码,但在实用性,灵活性,安全性上都有更好的表现。
IntentService
它相当于是Service和HandlerThread的结合体,就是在Service里面开了个线程,只是在所有的Message处理完毕之后,工作线程会自动结束,然后Service也会销毁。
Rxjava
观察者与被观察者模式,两个角色可以在不同的线程执行。