UDN-企业互联网技术人气社区

板块导航

浏览  : 631
回复  : 2

[讨论交流] Java开发教程重点(六)为线程安全类中添加新的原子...

[复制链接]
王许柔的头像 楼主
发表于 2016-8-3 10:30:31 | 显示全部楼层 |阅读模式
本帖最后由 王许柔 于 2016-8-3 10:48 编辑

  Java 类库中包含许多有用的”基础模块“类。通常,我们应该优先选择重用这些现有的类而不是创建新的类。:重用能降低开发工作量、开发风险(因为现有类都已经通过测试)以及维护成本。有时候,某个线程安全类能支持我们需要的所有操作,但更多的时候,现有的类只能支持大部分的操作,此时就需要在不破坏线程安全的情况下添加一个新的操作。

  假设我们需要一个线程安全的链表,他需要提供一个原子的”若没有则添加(Put-If-Absent)“的操作。同步的 List 类已经实现了大部分的功能,我们可以根据它提供的 contains 方法和 add 方法构造一个实现。

  可以有四种方法来实现这个原子操作。

  第一种方法,也是最安全的方法,便是修改原始类。

  但这通常是无法做到的,因为你可能无法访问或修改类的源代码。要想修改原始的类,就需要深刻理解代码中的同步策略,这样增加的功能才能与原有的设计保持一致。如果直接将新方法添加到类中,那么意味着实现同步策略的所有代码仍然处于一个源代码文件中,从而更容易理解与维护。

  第二种方法,可以扩展(继承)这个类—-假如原始类在设计的时候时考虑到了它的可扩展性。

  例如,我们可以设计一个 BetterVector 对 Vector 进行扩展,并添加了一个新方法 putIfAbsent。
  1. public class BetterVector<E> extends Vector<E>{
  2.         public synchronized boolean putIfAbsent(E x){
  3.                 boolean absent = !contains(x);
  4.                 if(absent)add(x);
  5.                 return absent;
  6.         }
  7. }
复制代码

  扩展 Vector 很简单,但并非所有的类都像 Vector 那样将状态向子类公开,因此也就不适合采用这种方法。

  ”扩展“方法比较脆弱,主要原因是 同步策略的实现被分离到了多个源代码文件中,如果底层类改变了同步策略,更改了不同的锁来保护状态,那么子类便会被破坏。

  第三种方法,使用辅助类,实现客户端加锁机制。

  对于某些类,比如 Collections.synchronizedList 封装的 ArrayList , 前两种方法都行不通,因为客户代码不知道在同步封装器工厂方法中返回的 List 对象的类型。这时候采用客户端加锁的方式,将扩展代码放到一个”辅助类“中。

  于是我们很自然的就写出 ListHelper 辅助类。
  1. public class ListHelper<E>{
  2.         public List<E> list = Collections.synchronizedList(new ArrayList<E>());
  3.         public synchronized boolean putIfAbsent(E x){
  4.                 boolean absent = !list.contains(x);
  5.                 if(absent)
  6.                         list.add(x);
  7.                 return absent;
  8.         }
  9. }
复制代码

  咋一看没问题,可是很遗憾,这种方式是错误的。

  虽然 putIfAbsent 已经声明为 synchronized ,但是它却是在 ListHelper 上加锁,而 List 却是用自己或内部对象的锁。 ListHelper 只是带来了同步的假象,

  在 Vector 和同步封装器类的文档中指出,他们是通过 Vector 或封装容器内部锁来支持客户端加锁。下面我们给出正确的客户端加锁。
  1.         public List<E> list = Collections.synchronizedList(new ArrayList<E>());
  2.         public boolean putIfAbsent(E x){
  3.                 synchronized (list){
  4.                         boolean absent = !list.contains(x);
  5.                         if(absent)
  6.                                 list.add(x);
  7.                 return absent;}
  8.         }
  9. }
复制代码

  通过添加一个原子操作来扩展类是脆弱的,因为它将类的加锁代码分布到多个类中。然而,客户端加锁却更加脆弱,因为它将类的加锁代码放到与其完全无关的其他类中。

  第四种方法,使用组合(Composition)的方式。
  1. public  class ImprovedList<T> implements List<T> {
  2.         public final List<T> list;

  3.         public ImprovedList(List<T> list) {
  4.                 this.list = list;
  5.         }

  6.         public synchronized boolean putIfAbsent(T x) {
  7.                 boolean absent = !list.contains(x);
  8.                 if (absent)
  9.                         list.add(x);
  10.                 return absent;
  11.         }
  12.         
  13.         //...按照类似的方式委托List的其他方法
  14. }
复制代码

  ImprovedList 通过自身的内置锁增加了一层额外的加锁。它并不关心底层的 List 是否是线程安全的,即使 List 不是线程安全的或者修改了它的枷锁方式,Improved 也会提供一致的加锁机制来实现线程安全性。虽然额外的同步层可能导致轻微的性能损失,但与模拟另一个对象的加锁策略相比,ImprovedList 更为健壮。事实上,我们使用了 Java 监视器模式来封装现有 List ,并且只要在类中拥有指向底层 List 的为意外不引用,就能确保线程安全性。

  扩展阅读:

  Java开发教程重点(汇总)

相关帖子

发表于 2016-8-3 14:12:25 | 显示全部楼层
赞一个
使用道具 举报

回复

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

关于我们
联系我们
  • 电话:010-86393388
  • 邮件:udn@yonyou.com
  • 地址:北京市海淀区北清路68号
移动客户端下载
关注我们
  • 微信公众号:yonyouudn
  • 扫描右侧二维码关注我们
  • 专注企业互联网的技术社区
版权所有:用友网络科技股份有限公司82041 京ICP备05007539号-11 京公网网备安1101080209224 Powered by Discuz!
快速回复 返回列表 返回顶部