1、线程是什么?
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。 一个进程包括由操作系统分配的内存空间,包含一个或多个线程。一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守护线程都结束运行后才能结束。 通俗来说,线程是 CPU 调度的最小单位,一个进程中可以包含多个线程,在 Android 中,一个进程通常是一个 App,App 中会有一个主线程,主线程可以用来操作界面元素,如果有耗时的操作,必须开启子线程执行,不然会出现 ANR,除此以外,进程间的数据是独立的,线程间的数据可以共享。
ANR:在 Android 上,如果你的应用有一段时间响应不够灵敏,系统会向用户显示一个对话框,这个对话框称为应用程序无响应对话框,也就是 ANR,不同的组件发生ANR的时间不一样,Activity 是 5 秒,BroadCastReceiver 是 10 秒,Service 是 20 秒(均为前台) 如何解决: 找到奔溃的地方 ——> 获取堆栈信息 ——> 如果看不到异常 Exception 日志,可能就是 ANR ——> 从堆栈中获取信息(经由页面) ——> 分析各个页面中的耗时操作,然后处理
2、基础概念
并行:多个 CPU 实例或多台机器同时执行一段代码,是真正的同时。 并发:通过 CPU调度算法,让用户看上去同时执行,实际上从 CPU 操作层面不是真正的同时。 线程安全:指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。线程不安全就意味着线程的调度顺序会影响最终结果,比如某段代码不加事务去并发访问。 线程同步:指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如某段代码加入 synchronized 关键字。线程安全的优先级高于性能优化。 原子性:一个操作或者一系列操作,要么全部执行要么全部不执行。数据库中的“事物”就是个典型的原子操作。 可见性:当一个线程修改了共享属性的值,其它线程能立刻看到共享属性值的更改。比如 JMM 分为主存和工作内存,共享属性的修改过程是在主存中读取并复制到工作内存中,在工作内存中修改完成之后,再刷新主存中的值。若线程 A 在工作内存中修改完成但还来得及刷新主存中的值,这时线程 B 访问该属性的值仍是旧值。这样可见性就没法保证。 有序性:程序运行时代码逻辑的顺序在实际执行中不一定有序,为了提高性能,编译器和处理器都会对代码进行重新排序。前提是,重新排序的结果要和单线程执行程序顺序一致。
3、线程的生命周期
一个线程完整的生命周期转换图,如下所示: 新建状态( New ):使用 new 关键字和 Thread 类或其子类建立一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。 就绪状态( Runnable ):当线程对象调用了 start() 方法之后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待 JVM 里线程调度器的调度。 运行状态( Running ):如果就绪状态的线程获取 CPU 资源,就可以执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。 阻塞状态( Blocked ):如果一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源之后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或获得设备资源后可以重新进入就绪状态。可以分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。该线程会释放占用的所有资源,JVM会把该线程放入“等待队列”中。进入这个状态后,是不能自动唤醒的必须依靠其他线程调用 notify() 或 notifyAll() 方法才能被唤醒。 同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用),则JVM会把该线程放入“锁池”中。 其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当 sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。 死亡状态( Dead ):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
4、线程的优先级
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。 注意:具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
5、线程管理
sleep():使一个线程处于睡眠(阻塞)状态,属于 Thread 类中的。特点:调用此方法过程中线程不会释放对象锁。它会导致程序暂停执行指定的时间,让出 cpu 给其他线程,但是它仍然保持对 Thread 的监控状态,直到指定的时间结束后又会自动恢复可运行状态( Runnable )。 join():等待该线程结束,才能执行其他线程,属于 Thread 类中的。通常用于在 main() 主线程内,等待其它线程完成再执行 main() 主线程。join 方法实现原理是通过 wait 方法, 当 main 线程调用 Thread.join() 时,main 线程会获得线程对象的锁,调用该对象的 wait(),直到该对象唤醒 main 线程 。这就意味着 main 线程调用 Thread.join 时,必须能够拿到线程对象的锁。 yield():当线程在 runnable 可运行状态时是处于被调度的线程,此时的调度顺序是不一定的。 Thread 类中的 yield() 方法可以让一个 running 状态的线程转入 runnable。 wait():使一个线程处于等待状态,属于 Object类 中的。特点:会释放持有的对象锁,进入等待此对象的锁定池( lock blocked pool ),直到针对此对象调用 notify()/notifyAll() 被唤醒,释放同步锁使线程回到可运行状态( Runnable )。 notify():使一个等待状态的线程唤醒。注意:并不能确切唤醒等待状态线程,是由 JVM 决定且不按优先级。 notifyAll():使所有等待状态的线程唤醒。注意:并不是给所有线程上锁,而是让它们竞争。 synchronized关键字:可以作为函数的修饰符,也可作为函数内的语句,也就是平时说的同步方法和同步语句块。使 Running 状态的线程加同步锁使其进入锁定池( lock blocked pool )。同步锁被释放后进入可运行状态( Runnable )。缺点是无法中断线程,也无法设置阻塞时长。
5.1 在线程中 Object#wait() 和 Thread#sleep(xxx) 方法的区别?
Object#wait() 方法既释放 cpu,又释放锁。 Thread#sleep(xxx) 方法只释放 cpu,但是不释放锁。
6、线程实现的三种方式
继承 Thread 类实现多线程。 实现 Runnable 接口方式实现多线程。 实现 Callable 接口 + FutureTask包装器方式实现多线程。
6.1 继承 Thread 类实现多线程优劣势:
优势:编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。 劣势:由于 Java 是单继承,所以如果线程类已经继承了 Thread 类,就不能再继承其它父类。
6.2 实现 Runnable 或 Callable 接口方式实现多线程优劣势:
优势:线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将 CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。 劣势:编程稍微复杂,如果要访问当前线程,则必须使用 Thread.currentThread() 方法。
6.3 注意事项:
在通过实现 Callable 接口方式实现多线程过程中,Thread#start() 方法只能执行一次,即开启线程只能执行一次。而如果直接调用 Thread#run() 则该方法是在被调用线程执行。 调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值,调用 get() 方法会阻塞线程。
6.4 示例代码:
public class MultiThreadDemo {
public static void main ( String[ ] args) {
MyThread myThread1_1 = new MyThread ( ) ;
MyThread myThread1_2 = new MyThread ( ) ;
myThread1_1. start ( ) ;
myThread1_2. start ( ) ;
MyRunnable myRunnable = new MyRunnable ( ) ;
new Thread ( myRunnable) . start ( ) ;
new Thread ( myRunnable) . start ( ) ;
MyCallable callable = new MyCallable ( "Hello 多线程" ) ;
FutureTask< Integer> task1 = new FutureTask < Integer> ( callable) ;
new Thread ( task1) . start ( ) ;
FutureTask< Integer> task2 = new FutureTask < Integer> ( callable) ;
new Thread ( task2) . start ( ) ;
}
static class MyThread extends Thread {
@Override
public void run ( ) {
System. out. println ( "继承 Thread 类实现多线程=>" + this . getName ( ) + "运行" ) ;
}
}
static class MyRunnable implements Runnable {
@Override
public void run ( ) {
System. out. println ( "实现 Runnable 接口方式实现多线程=>" + Thread. currentThread ( ) . getName ( ) + "运行" ) ;
}
}
static class MyCallable implements Callable {
private String str;
public MyCallable ( String str) {
this . str = str;
}
@Override
public Boolean call ( ) throws Exception {
System. out. println ( "实现 Callable 接口 + FutureTask包装器方式实现多线程=>" + Thread. currentThread ( ) . getName ( ) + "运行,传递的值为:" + str) ;
return true ;
}
}
}
7、线程中断的方式
使用 violate boolean 变量标志位来退出线程。(推荐) 使用 interrupt() 方法中断线程,但是线程不一定会终止。因为如果线程被 Object.wait、Thread.join 和 Thread.sleep 三种方法之一阻塞,此时调用该线程的 interrupt() 方法,那么该线程将抛出一个 InterruptedException 中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt() 将不起作用,直到执行到 wait()、sleep()、join() 时,才马上会抛出 InterruptedException 。 使用 stop 方法强行终止线程。(被弃用)(1)调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作,包括在 catch 或 finally 语句中的,并抛出 ThreadDeath 异常(通常情况下此异常不需要显示的捕获),因此可能会导致一些清理性的工作的得不到完成,如文件,数据库等的关闭。(2)调用 stop() 方法会立即释放该线程所持有的所有的锁,导致数据得不到同步,出现数据不一致的问题。
public class ExitThreadDemo {
public static void main ( String[ ] args) {
exitThread_1 ( ) ;
exitThread_2 ( ) ;
}
public static volatile boolean isThreadExit = false ;
private static void exitThread_1 ( ) {
new Thread ( ) {
public void run ( ) {
System. out. println ( "线程1启动了" ) ;
while ( true ) {
if ( ! isThreadExit) {
try {
System. out. println ( "线程1运行中..." ) ;
Thread. sleep ( 1000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
} else {
System. out. println ( "线程1结束了" ) ;
break ;
}
}
}
} . start ( ) ;
try {
Thread. sleep ( 1000 * 5 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
isThreadExit = true ;
}
private static void exitThread_2 ( ) {
Thread thread = new Thread ( ) {
public void run ( ) {
System. out. println ( "线程2启动了" ) ;
while ( true ) {
try {
System. out. println ( "线程2运行中..." ) ;
Thread. sleep ( 1000 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
System. out. println ( "线程2结束了" ) ;
return ;
}
}
}
} ;
thread. start ( ) ;
try {
Thread. sleep ( 1000 * 5 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
thread. interrupt ( ) ;
}
}
8、线程的几个主要概念
线程同步 线程间通信 线程死锁 线程控制:挂起、停止和恢复 多线程的使用注意事项:有效利用多线程的关键是理解程序是并发执行而不是串行执行的。例如:程序中有两个子系统需要并发执行,这时候就需要利用多线程编程。通过对多线程的使用,可以编写出非常高效的程序。不过请注意,如果你创建太多的线程,程序执行的效率实际上是降低了,而不是提升了。请记住,上下文的切换开销也很重要,如果你创建了太多的线程,CPU 花费在上下文的切换的时间将多于执行程序的时间!
9、Android 线程间通信方式
(1)Handler (2)Activity.runOnUiThread(Runnable) (3)View.post(Runnable)/View.postDelayed(Runnable,long) (4)AsyncTask 等等…
10、Android 进程间通信方式
(1)Bundle:由于 Activity,Service,Receiver 都是可以通过 Intent 来携带 Bundle 传输数据的,所以我们可以在一个进程中通过 Intent 将携带数据的 Bundle 发送到另一个进程的组件。但是有一个缺点,就是无法传输 Bundle 不支持的数据类型。 (2)ContentProvider:ContentProvider 是 Android 四大组件之一,以表格的方式来储存数据,提供给外界,即 Content Provider 可以跨进程访问其他应用程序中的数据。用法是继承 ContentProvider,实现 onCreate,query,update,insert,delete 和 getType 方法,onCreate 是负责创建时做一些初始化的工作,增删查改的方法就是对数据的查询和修改,getType 是返回一个 String,表示 Uri 请求的类型。注册完后就可以使用 ContentResolver 去请求指定的 Uri。 (3)文件:两个进程可以到同一个文件去交换数据,我们不仅可以保存文本文件,还可以将对象持久化到文件,从另一个文件恢复。要注意的是,当并发读/写时可能会出现并发的问题。 (4)Broadcast:Broadcast 可以向 Android 系统中所有应用程序发送广播,而需要跨进程通讯的应用程序可以监听这些广播。 (5)AIDL方式:Service 和 Content Provider 类似,也可以访问其他应用程序中的数据,Content Provider 返回的是 Cursor 对象,而 Service 返回的是 Java 对象,这种可以跨进程通讯的服务叫 AIDL 服务。AIDL 通过定义服务端暴露的接口,以提供给客户端来调用,AIDL 使服务器可以并行处理,而 Messenger 封装了 AIDL 之后只能串行运行,所以 Messenger 一般用作消息传递。 (6)Messenger:Messenger 是基于 AIDL 实现的,服务端(被动方)提供一个 Service 来处理客户端(主动方)连接,维护一个 Handler 来创建 Messenger,在 onBind 时返回 Messenger 的 binder。双方用 Messenger 来发送数据,用 Handler 来处理数据。Messenger 处理数据依靠 Handler,所以是串行的,也就是说,Handler 接到多个 message 时,就要排队依次处理。 (7)Socket:Socket 方法是通过网络来进行数据交换,注意的是要在子线程请求,不然会堵塞主线程。客户端和服务端建立连接之后即可不断传输数据,比较适合实时的数据传输。 参考文献:https://cloud.tencent.com/developer/article/1441385