这篇文章介绍的几个问题:
1 、进程和线程,以及区别
2、Android中的进程
3、多线程
4、线程同步
5、进程间通信
6、Handler、MessageQuere、Runnable、Looper
一 、进程和线程,以及区别
进程(Process):当一个程序进入内存运行时,即变成一个进程。进程是处于运行过程中的程序,是程序的一个运行实例。 进程是操作系统进行资源分配和调度的一个独立单位。
线程(Thread):线程是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
并发和并行
并发:是指在同一时间点只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
并行:指在同一时间点,有多条指令在多个处理器上同时执行。
·
进程和线程的区别
- 线程是进程的组成部分,一个进程可以有很多线程,每条线程并行执行不同的任务。
- 不同的进程使用不同的内存空间,而线程与父进程的其他线程共享父进程的所拥有的全部资源。这样编程方便了,但是要更加小心。
- 别把内存空间和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据。线程拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源。
- 线程的调度和管理由进程本身负责完成。操作系统对进程进行调度,管理和资源分配。
二 、Android中的进程
Android存在五种级别的进程:前台进程、可见进程、服务进程、后台进程、空进程。
- 前台进程(foreground process):前台进程是用户当前正在使用的进程。
存在前台进程的条件:
- 进程中的某个Activity正在与用户进行交互(Activity的onResume()方法被调用);
- 绑定到与当前用户正在交互的activity的Service所在的进程;
- 进程中的某个Service正运行在前台,即这个service的startForeground()方法被调用;
- 进程中的某个Service正在执行生命周期回调方法(比如,onCreate(),onStart(),或者onDeatroy());
- 进程中的BroadcastReceiver正在执行onReceive()方法;
- 可见进程(visible process):可视进程是指没有前台运行的组件,但仍然会对用户在屏幕看到的内容造成影响的进程。
存在可见进程的条件:
- 进程运行的Activity不在前台,但仍然是可见的(调用了onPause()方法)。这种情况可能是这样的,正在前台运行的Activity启动了一个对话框,这个对话框悬浮在这个activity之上,但仍有部分可见;
- 进程中的Service绑定到了一个可视(或前台)的activity(该activity已调用了onPause()方法);
- 服务进程(service process):运行着一个通过startService() 方法启动的service,这个service不属于上面提到的2种更高重要性的。
service所在的进程虽然对用户不是直接可见的,但是他们执行了用户非常关注的任务(比如播放mp3,从网络下载数据)。只要前台进程和可见进程有足够的内存,系统不会回收他们。
- 后台进程(background process):后台进程是指进程中的activity当前对用户来说不可见(这个activity调用了onStop()方法)。
后台进程不会对用户的体验造成任何影响,并且系统可以在前台进程、可视进程、服务继承需要内存资源的时候会杀死后台进程。通常会有很多后台进程运行,并且这些后台进程保存在一个最近使用列表中,这样做的好处就是保证用户最近看到的进程最后被杀死。如果一个activity已经正确的实现了生命周期方法,并且保存了当前的状态,那么系统杀死这些后台进程对用户的可视效果来说的话,没有任何影响,因为当用户返回回来的时候,这个activity已经保存了所有的可视状态。
- 空进程(empty process):一个空进程没有任何运行的程序组件。
系统保持空进程存在的唯一原因就是为了缓存方面的考虑,这样做主要是为了提高组件的启动时间。系统经常会杀死这些空进程来保持整个系统资源和内核缓存之间的平衡。
因为一个正在运行的服务所在的进程的重要性高于一个处于后台的activity所在的进程,所以根据这一点,如果一个activity如果要执行需要长时间运行的操作的话,这个activity最好为该操作启动一个新的服务,而不是仅仅创建一个工作线程,尤其是当这个工作线程运行的时间可能比该activity的运行时间还长的时候。
三 、多线程
为什么Android是线程不安全的?
Android单线程模型的两个原则:不能阻塞UI线程;不要再UI线程外访问Android UI toolkit。
也就是说,在非UI线程操作UI 和 在UI线程进行耗时操作都会出现错误。
既然耗时操作不能再UI线程中进行,我们当然会想到新开线程处理耗时操作,这就会出现多线程的问题。同时,耗时操作在单独的进程中完成后,得到的结果必须要在UI线程展示,就会涉及进程间通信的问题。
Java创建线程的方法:
- 继承Thread类创建线程类
1、定义Thread类的子类,重写该类的run()方法。该方法为线程执行体;
2、创建Thread子类的实例。即线程对象;
3、调用线程对象的start()方法启动该线程;
- 实现Runnable接口创建线程类
1、定义Runnable接口的实现类,重写该接口的run()方法。该方法为线程执行体;
2、创建Runnable实现类的实例。并以此实例作为Thread的target来创建Thread对象。该Thread对象才是真正的线程对象;
3、调用线程对象(该Thread对象)的start()方法启动该线程;
- 使用Callable和Future创建线程
Android中多线程的几种工具类:
- AsyncTask
- HandlerThread
- ThreadPool
- IntentService
详见:
四 、线程同步
线程同步:当使用多个线程来访问同一个数据时,非常容易出现线程安全问题(比如多个线程都在操作同一数据导致数据不一致),所以我们用同步机制来解决这些问题。线程同步的目的就是避免线程“同步”执行。
同步机制既可以同步代码块、又可以同步方法:
//同步代码块
synchronized (obj) {
...
}
//同步方法
public synchronized void methodName() {
....
}
当使用synchronized 来修饰某个共享资源时,当某个线程获得共享资源的锁后就可以执行相应的代码段,直到该线程运行完该代码段后才释放对该共享资源的锁,让其他线程有机会执行对该共享资源的修改。当某个线程占有某个共享资源的锁时,如果另外一个线程也想获得这把锁运行就需要使用wait() 和notify()/notifyAll()方法来进行线程通讯了。
线程同步的特征
1、 如果一个同步代码块和非同步代码块同时操作共享资源,仍然会造成对共享资源的竞争。因为当一个线程执行一个对象的同步代码块时,其他的线程仍然可以执行对象的非同步代码块。(所谓的线程之间保持同步,是指不同的线程在执行同一个对象的同步代码块时,因为要获得对象的同步锁而互相牵制)
2、 每个对象都有唯一的同步锁
3、 在静态方法前面可以使用synchronized修饰符。
4、 当一个线程开始执行同步代码块时,并不意味着必须以不间断的方式运行,进入同步代码块的线程可以执行Thread.sleep()或执行Thread.yield()方法,此时它并不释放对象锁,只是把运行的机会让给其他的线程。
5、 Synchronized声明不会被继承,如果一个用synchronized修饰的方法被子类覆盖,那么子类中这个方法不在保持同步,除非用synchronized修饰。
死锁
线程1独占(锁定)资源A,等待获得资源B后,才能继续执行;
线程2独占(锁定)资源B,等待获得资源A后,才能继续执行;
这样就会发生死锁,程序无法正常执行。
为避免死锁,当几个线程都要访问共享资源A、B、C 时,保证每个线程都按照同样的顺序去访问他们。
lock和synchronized:
摘自:百度知道
总的来说,lock更加灵活。主要相同点:Lock能完成synchronized所实现的所有功能
不同:
1.ReentrantLock功能性方面更全面,比如时间锁等候,可中断锁等候,锁投票等,因此更有扩展性。在多个条件变量和高度竞争锁的地方,用ReentrantLock更合适,ReentrantLock还提供了Condition,对线程的等待和唤醒等操作更加灵活,一个ReentrantLock可以有多个Condition实例,所以更有扩展性。
2.ReentrantLock必须在finally中释放锁,否则后果很严重,编码角度来说使用synchronized更加简单,不容易遗漏或者出错。
3.ReentrantLock 的性能比synchronized会好点。
4.ReentrantLock提供了可轮询的锁请求,他可以尝试的去取得锁,如果取得成功则继续处理,取得不成功,可以等下次运行的时候处理,所以不容易产生死锁,而synchronized则一旦进入锁请求要么成功,要么一直阻塞,所以更容易产生死锁。1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
c) tryLock (long timeout, TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
五 、进程间通信
进程间通信:Inter-process communication (IPC)是指运行在不同进程(无论是否是同一台机器)中的若干线程间的数据交换。
进程间通信的两种方法:文件共享、操作系统提供的公共信息机制。
计算机进程间通信的几种机制:
- 共享内存(Shared Memory)
通过访问公共内存空间。
- 管道(Pipe)
管道也是操作系统中常见的一种进程间通信方式,管道是单向的,如果同时有读和写的操作,就需要建立两根管道。
- UDS(Unix Domain Socket)
Socket在网络通信领域获得了广泛的应用,被称为Network Socket,对于同一台机器的进程间通信,他也完全能够胜任。UDS是专门针对单机内的进程间通信提出来的,有时也被成为IPC Socket。
Android中使用最多的一种IPC机制是Binder,其次就是UDS。
- RPC (Remote Procedure Calls)
RPC设计的通信双方通常运行与两台不同的机器中。
六 、Handler、MessageQuere、Runnable、Looper
message
message是消息内容;
messageQuere
messageQuere是消息队列,里面存很多的message;
handler
消息的真正处理者;
通知MQ它要执行一个任务(sendMessage),并在loop到自己的时候执行该任务(handleMessage),整个过程是异步的。
looper
looper管理进程中的MessageQuere,负责循环消息;
runnable
Runnable和Message可以被压入某个MessageQueue中,形成一个集合 ;
详细参考:林学森的Android专栏