【安卓开发艺术探索】第2章 进程线程通信与同步 笔记

这一章本来是讲安卓跨进程通信的,也就是IPC。但由于实在是纠结于进程和线程在同步和通信上的异同,就索性查了一大波资料,结合自己的认识,把这一篇扩充成了进程的通信和同步,以及线程的通信和同步。希望以此告别纠结之旅。

线程同步方式

我查阅了很多网上的资料,但其实发觉很多的博文上可能局限于当时的版本原因,还有一些同步的方式并没能列举,下面我将列举所有的线程同步方式,如有不足请指出。

1、synchronized

同步锁,可以用来修饰方法,修饰代码块,这个网上例子很多,也比较容易使用,但是有一些地方可能容易混淆。

  • 修饰成员方法时,当有多个线程同时访问某一个对象的多个同步方法时,只有得到同步锁的可以执行,其他的都会被阻塞,而此对象的非同步方法不会阻塞非相同对象的所有方法不会阻塞
  • 修饰类方法时,当有线程进入方法时,其他访问类的其他同步方法的线程将被阻塞。对象的同步方法访问不受影响。
  • 修饰代码块的时候,当多个线程运行到synchronized的代码块时,如果上锁的对象已经有线程取得了同步锁,则其他线程被阻塞。这里比较的是其引用的对象是否已经有锁,假如String a="sss",b="sss";由于a==b是成立的,所以当有线程获得了a的锁时,其他尝试获取b的锁的线程也会被阻塞,因为a和b所指向的对象时相同的。这个和a.equal(b)无关。
    即有线程进入了synchronized(a){...}代码块,则尝试进入synchronized(b){...}的也会被阻塞,因为a==b为真。

2、wait() notify()方法

这个不多讲。Object自带的方法,虽然不觉得好用。

3、Lock、ReadWriteLock接口

实现这两个接口的分别是ReentrantLockReentrantReadWriteLock,通过在需要同步的代码位置写上类似

        myLock.lock();
        myLock.unlock();
        myLock.readLock().lock();
        myLock.readLock().unlock();
        myLock.writeLock().lock();
        myLock.writeLock().unlock();

即可申请锁和解锁。构造函数里可设置是否为公平锁,公平锁是先来先得的形式,不公平锁是随机得的形式,不分先后,默认不公平。建议在try中申请,finally中释放。

4、Semaphore信号量

可以参考这个链接了解:http://www.cnblogs.com/whgw/archive/2011/09/29/2195555.html
通过Semaphore semp = new Semaphore(5);来构造一个初始的信号量,再在需要申请信号量的代码中写下semp.acquire(2);申请不定数量的资源,默认为1,在完成后释放即可,释放时也可释放自定数量的资源semp.release(2);默认释放1。

5、CountDownLatch和CyclicBarrier

这两个有些相似,相同点是都有点类似于等待一组线程完成任务后再继续进行。
构造CountDownLatch:CountDownLatch latch = new CountDownLatch(3);,再在子线程中完成部分任务后调用latch.countDown();即可让计数-1,这里不会阻塞代码运行。而真正阻塞执行的地方是调用了latch.await();的地方。只有等计数器为0是才会继续执行。
构造CyclicBarrier:CyclicBarrier barrier=new CyclicBarrier(3);构造方法还支持添加一个Runnable作为结束时执行的方法。在需要等待其他线程的地方调用barrier.await();即可,直到计数器为0时恢复执行。
附上一个可以参考的链接:http://blog.csdn.net/shihuacai/article/details/8856407

6、volatile

记清楚,volatile只是保证两点:

  1. 在访问volatile被修饰的数据时,一定会从堆中读取。或者说一定保证读到最新的数据。
  2. 禁止指令重排volatile修饰的数据。

以上意味着如果在多线程中修改volatile修饰的数据依然会不安全,但如果只有一个线程修改,其他线程只是访问,则可以满足需要,而且这比其他同步方式更高效。另外,不要滥用volatile,因为它会阻止相关数据的指令重排,也就意味着降低一定的效率。

有一些博客上讲了ThreadLocal,但个人认为不太实用,甚至觉得没法真正满足线程同步的需求,就没列举。

线程通信方式

1、Handler和基于Handler的方式

比如Activity的runOnUiThread,View的post系列方法都是基于Handler机制实现的。关于Handler机制,我的另一篇博文有所介绍:http://blog.csdn.net/qq_22123283/article/details/70195780

2、PipedInputStream、PipedOutputStream、PipedReader、PipedWritter

关于InputStream和Reader以及OutputStream和Writter的区别,根据此链接:http://blog.sina.com.cn/s/blog_6d3183b50101cri5.html ,可以认为区别就是Reader是按字符读取,读取的是字符流,而InputStream是按字节读取,读取的是字节流。
这个方式用起来比较容易,但是每个管道都是单向的。

3、同步方式(如果不进行区分)

同步也可以认为是一种线程的通信,如果不特意区分的话,上述的线程同步方式均可作为通信方式,只不过其通信的意义不太一样。

进程通信方式(针对安卓环境)

先列一下,之后补充细节。

1、Intent+Bundle

2、文件共享

SharedPreferences除外,因为其有缓冲机制。

3、Messenger

使用Messenger时注意,它是串行处理消息的。一个典型的客户端和服务端双向通过Messenger通信的流程是:

  1. 创建客户端Handler,创建客户端Messenger clientMessenger=new Messenger(clientHandler);
  2. 服务端创建Handler,创建Messenger serviceMessenger=new Messenger(serviceHandler);onBind方法中返回serviceMessenger.getBinder()
  3. 客户端在ServiceConnectiononServiceConnected方法中获取服务器的Messenger serviceMessenger=new Messenger(binder);,通过这个Messenger就可以向服务端发送Message了。如果需要服务端回应,需要写message.replyTo=clientMessenger;即可。

和所有的AIDL一样,如果是主线程进行这样的通信,服务端的代码一定不能耗时过久,因为客户端访问的线程是被阻塞/挂起的。

4、AIDL

此方式建议多上手练习,比较复杂一些。流程主要如下:

  1. 如果需要用到自己实现Parcelable接口的对象,则需要新建一个对应的AIDL文件,在里面声明parcelable 'Class'即可。比如parcelable Book;
  2. 创建服务端要实现的接口AIDL文件。接口方法的参数如果要用到上述的Parcelable对象,则需要用import导入,同时需要在参数类型前面加上inoutinout,因为底层实现是有不同的开销的。
  3. build项目,自动生成对应的Java代码。然后在服务端实现接口,方法类似Binder mBinder = new IBookManager.Stub() { //implement methods... }。并在服务端的onBind方法中返回此binder
  4. 客户端在onServiceConnected方法中用类似IBookManager bookManager = IBookManager.Stub.asInterface(service);的代码将binder转换成接口,并正常调用接口方法即可。同理此客户端线程会被挂起/阻塞。

进阶问题:

  1. 使用RemoteCallbackList<T>来注册回调接口,其他方式实现的方法将不能取消注册,因为binder最后返回的完全不是所传递的对象,所以更不可能在服务端找到对应客户端的对象。而使用这个可以解决问题。
  2. AIDL过多使用时,考虑使用BinderPool方案,这其实是在服务端完成多个AIDL接口的实现,而在onBind方法中只返回binderPoolImpl的实现,BinderPool也在服务端实现,主要就是在一个IBinder queryBinder(int binderCode)方法中,根据bidnerCode去选择返回要返回的IBinder即可。于是就可以在一个Service的前提下实现多个AIDL通信方式。
  3. 请注意Binder的安全性,在onBind环节检查Permission或在接口方法中检查权限,防止任何应用都能访问。

5、ContentProvider

需要重写6个方法,值得注意的是onCreate方法是运行在请求此provider的线程中的,其他方法则是运行在BinderPool中。添加PermissionreadPermission, writePermission增强安全性。UriMatcher的使用可以帮助分析要选择的table

6、Socket

localhost的自定端口发起通信连接即可。

进程同步方式(操作系统知识)

这方面更多是指操作系统上的知识,可能在不同的具体系统上,方式不一定相同,可能更多可能更少。其中前2个方式是进程同步,后面4个是进程通信,因为有的情况下可以认为通信和同步是一个意思,所以都列在这里了。

1、信号量

2、管程

3、共享存储器系统(进程通信)

4、管道通信系统(进程通信)

5、消息传递系统(进程通信)

6、套接字(进程通信)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值