`
足至迹留
  • 浏览: 485460 次
  • 性别: Icon_minigender_1
  • 来自: OnePiece
社区版块
存档分类
最新评论

<进阶-3> 对象的组合

阅读更多
[...续上文]
3. 对象的组合
前面我们已经介绍了关于线程安全和同步的一些基础知识。然而,我们并不希望每次内存访问都进行分析以确保程序是线程安全的,而是希望将一些现有的线程安全组件组合为更大规模的组件或程序。下面介绍的一些组合模式,能够使一个类更容易成为线程安全的,并且在维护这些类时不会无意中破坏类的安全性保证。

3.1 设计线程安全的类
在线程安全的程序中,虽然可以将程序的所有状态都保存在共有的静态域中,但与将状态封装起来的程序比,还是后者的安全性更容易得到验证,并且前者在修改时更难以始终确保线程安全性。通过使用封装技术,可以使得在不对整个程序进行分析的情况下就可以判断一个类是否是线程安全的。
在设计线程安全类的过程中,需要同时包含三个基本要素:
1. 找出构成对象状态的所有变量。
2. 找出约束状态变量的不变性条件
3. 建立对象状态的并发访问管理策略。

3.2 实例封闭
如果对象不是线程安全的,可以通过各种技术使其在多线程程序中安全地使用。可以确保该对象只能由单个线程访问(线程封闭),或者通过一个锁来保护对该对象的所有访问。
(可以对比上一篇2.3节的线程封闭)。
封装简化了线程安全类的实现过程,它提供了一种实例封闭机制(Instance Confinement)。当一个对象被封装在另一个对象中时,能够访问被封装对象的所有代码路径都是已知的。与对象可以由整个程序访问的情况相比,更易于对代码进行分析。通过将封闭机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用非线程安全的对象。
示例:
public class PersonSet
{
  private final Set<Person> mySet = new HashSet<Person>();

  public synchronized void addPerson(Person p)
  {
      mySet.add(p);
  }

  public synchronized boolean containsPerson(Person p)
  {
      return mySet.contains(p);
  }
}


这里是把Person对象封装在PersonSet中,保证对PersonSet的访问是线程安全的,不管Person类是否线程安全。但如果要线程安全的访问Person对象时,就需要额外的同步,或者将Person类也变成线程安全的类。比如自己在创建自己使用的简单缓存类时就可以选择这种方式。

实例封闭是构成线程安全类的一个最简单方式,它还使得在锁策略的选择上有更多的灵活性。
在java平台的类库中还有很多线程封闭的示例,其中有些类的唯一用途就是将非线程安全的类转化为线程安全的类,如Collections.synchronizedList及其类似方法。这些工厂方法通过“装饰器”模式将容器类封装在一个同步的包装器对象中。

3.2.1 java监视器模式
java监视器模式仅仅是一种编写代码的约定,对任何一种锁对象,只要自始至终都使用该锁对象,都可以用来保护对象的状态。如:
public class PrivateLock
{
    private final Object myLock = new Object();

    Widget widget;

    void someMethod()
    {
        synchronized(myLock)
        {
            //访问或修改Widget的状态
        }
    }
}

使用私有的锁对象而不是对象的内置锁(或任何其他可通过共有方式访问的锁)有许多优点,私有的锁对象可以将锁封装起来,使客户代码无法得到锁。

3.3 线程安全性的委托
大多数对象都是组合对象。当从头开始创建一个类,或将多个非线程安全的类组合成一个类时,java监视器模式非常有用。但是,如果类中的各个组件都已经是线程安全的,会是什么情况呢?是否需要增加一个额外的线程安全层?答案是“视情况而定”。

前面<进阶-1>篇1.2.2节的例子,CountingFactorizer类中,我们在一个无状态的类中增加了一个AtomicLong的域,并且得到的组合对象是线程安全的。由于CountingFactorizer的状态就是AtomicLong的状态,而AtomicLong是线程安全的,因此CountingFactorizer不会对counter的状态施加额外的有效性约束,所以很容易知道CountingFactorizer是线程安全的。我们可以说CountingFactorizer将它的线程安全性委托给AtomicLong来保证。

3.3.1 独立的状态变量
前面的委托示例仅仅委托给了单个线程安全的状态变量。我们还可以将线程安全性委托给多个状态变量,只要这些变量是彼此独立的,即组合而成的类并不会再其包含的多个状态变量上增加任何不变性条件。

3.3.2 委托失效时
大多数组合对象都不会像上面那么简单,在他们的状态变量之间会存在某些不变性条件,比如两个值一个必须比另一个小,等,这些就存在先检查后执行的复合操作。这时,类本身必须提供自己的加锁机制以确保这些复合操作都是原子操作。

3.3.3 发布底层的状态变量
当把线程安全性委托给某个对象的底层状态变量时,要发布这些变量必须谨慎分析。如果一个状态变量是线程安全的,并且没有任何不变性条件约束他的值,在变量的操作上也不存在任何不允许的转换,那么就可以安全地发布这个变量。

3.4 在现有的线程安全类中添加功能
Java类库中包含许多有用的“基础模块”类。通常,我们应该优先选择重用这些现有的类而不是创建新的类。但有时候某个现有的线程安全类只能支撑我们大部分的操作,此时就需要在不破坏线程安全性的情况下添加一个新的操作。

假如我们需要一个线程安全的链表,提供一个原子的“若没有则添加(put-if-absent)”的操作。同步的list类已经实现了大部分功能,但没有putIfAbsent(一些并发包里的集合是有这个操作的)。
一,要添加一个新的原子操作,最安全的方法是修改原始的类,但这通常无法做到,可能无法修改类的源码。
二,另一种方法是扩展这个类,假定可以继承这个类。排除一些类无法被继承,这种方法仍然比修改源码更加脆弱。因为现在的同步策略实现被分布在多个源码文件中,如果底层的类改变了同步策略并选择了不同的锁来保护它的状态变量,那么子类会被破坏。

3.4.1 客户端加锁机制
拿Collections.synchronizedList封装的ArrayList为例,上面的两种方法都不可行。因为客户代码并不知道在同步封装器工厂方法中返回的list对象的类型。这时可用第三种策略,通过“辅助类”扩展类的功能。
正确的做法:
public class ListHelper<E>
{
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public boolean putIfAbsent(E x)
    {
        synchronized (list)
        {
            boolean absent = !list.contains(x);
            
            if (absent)
            {
                list.add(x);
            }
        
             return absent;
         }
     }
}


容易误导的错误做法:
public class ListHelper<E>
{
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());

    public synchronized boolean putIfAbsent(E x)
    {
        boolean absent = !list.contains(x);
        
        if (absent)
        {
            list.add(x);
        }
 
        return absent;
    }
}

为什么这种方式不安全呢?问题是在错误的锁上进行了同步,这并不是list本身需要的锁。

客户端加锁是脆弱的,它将对原始类的加锁代码放到与类完全无关的其他类(辅助类或使用类)中。客户端加锁机制与继承类机制有许多共同点,二者都将派生类的行为与基类的实现耦合在一起。

3.4.2 组合
当为现有的类添加一个原子操作时,一个更好的方法:组合。
public class ImprovedList<T> implements List<T> 
{
    private final List<T> list;

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

    public synchronized boolean putIfAbsent(T x)
    {
        boolean absent = !list.contains(x);
     
        if (absent)
        {
            list.add(x);
        }
     
        return absent;
    }

    public synchronized void clear()
    {
        list.clear();
    }
}


看完之后我们发现,其实设计模式里也是推崇组合代替继承,比如代理模式,装饰模式等。
0
0
分享到:
评论

相关推荐

    PB进阶代码(很全的)

    《PowerBuilder 9.0进阶开发篇》(实例与技巧篇)配书光盘使用说明 本光盘内容为本书所有实例的源代码。如下: 第1章 窗口、控件与界面 1.1 类Outlook工具栏界面实现 -- Outlook 1.2 制作Splash窗口 -- Splash ...

    Python零基础速成班-第9讲-Python面向对象编程(上),对象和类、初始化、继承、重写、多态、类方法、组合.ipynb

    本教程设置3级标题,对知识点进行分类,内容上由基础+进阶的方式呈现,练习题也设置了必做和挑战项目,分别适用于初学者和进阶学习者。 整个教程大概有80个学时,同时也引入图像识别基础、算法基础、小游戏、爬虫、...

    Python基于Django实现的框架图书管理系统源代码+文档说明+sql文件

    &lt;项目介绍&gt; 该资源内项目源码是个人的毕设,代码都测试ok,都是运行成功后才上传资源,答辩评审平均分达到96分,放心下载使用! 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! ...

    DirectX高级动画制作 电子书+源代码

    5.4 从.X文件中读取动画数据 5.5 匹配动画到骨骼上 5.6 更新动画 5.7 从其他途径获取骨骼网格模型数据 5.8 运行示范程序 第6章 混合的骨骼动画 6.1 混合骨骼动画 6.2 组合变换 6.3 增强的骨骼动画对象 ...

    产品经理学习视频

    3-竞品分析对象选择.mp4 4-竞品分析渠道和内容.mp4 5-SWOT竞品分析.mp4 6-$APPEALS竞品分析.mp4 7-竞品分析ppt形式.mp4 8-竞品分析word形式.mp4 竞品分析.xmind 5深入理解产品经理 1-产品思维.mp4 2-产品...

    Python从基础到进阶——「模块&面向对象编程」(1)模块和类

    目录模块:用来组织代码包:用来组织模块类:用来组合数据和功能类的概念创建类和实例类的操作 模块:用来组织代码 概念/目的:为了避免再次进入python解释器时重新定义(函数和变量),并将过长的程序进行拆分管理...

    Python基于Django框架图书管理系统+源代码+文档说明

    &lt;项目介绍&gt; 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载...

    ThinkPHP5图书查询借阅管理网站+源代码+文档说明+数据库.zip

    &lt;项目介绍&gt; 1、该资源内项目代码都经过测试运行成功,功能ok的情况下才上传的,请放心下载使用! 2、本项目适合计算机相关专业(如计科、人工智能、通信工程、自动化、电子信息等)的在校学生、老师或者企业员工下载...

    Flash ActionScript 3.0高级动画教程

    进阶教程 总结 第五章 二级 输入设备:摄像头和麦克风 摄像头和麦克风 输入的声音 声控游戏 活跃事件 输入的视频 视频尺寸和质量 视频和位图 反转图像 分析像素 分析颜色 将跟踪颜色视作输入 分析移动区域 边缘检测 ...

    .net技术资料大全(语言规范 源码教程 学习笔记 技术资料 .net代码生成器)

    DataSet对象.txt DotNET WinForm FAQ 16个.txt excel打印.txt EXCEL导出.txt EXCEL中合并单元格.txt mail.txt NET在RichTextBox控件加入图片(类似QQ).txt send.txt SQL储存过程等的解密.txt VisualC#打造...

    ASP升级.net资料大全(c#入门 语言规范 源码教程 学习笔记 技术资料 面试题 asp与.net代码生成器)

    DataSet对象.txt DotNET WinForm FAQ 16个.txt excel打印.txt EXCEL导出.txt EXCEL中合并单元格.txt mail.txt NET在RichTextBox控件加入图片(类似QQ).txt send.txt SQL储存过程等的解密.txt VisualC#打造...

    asp.net知识库

    与DotNet数据对象结合的自定义数据对象设计 (一) 数据对象与DataRow ASP.NET中大结果集的分页[翻译] .net 2.0 访问Oracle --与Sql Server的差异,注意事项,常见异常 Ado.net 与NHibernate的关系? 动态创建数据库...

    JavaScript DOM高级程序设计 Part I

    JavaScript DOM高级程序设计 JavaScript进阶/写库必备。文档带有章节目录。 第一章 遵循最佳实践 第二章 创建可重用的对象 第三章 DOM2核心和DOM2 HTML 第四章 响应用户操作事件 第五章 动态修改样式和层叠样式表 第...

    JavaScript DOM高级程序设计 Part II

    JavaScript DOM高级程序设计 JavaScript进阶/写库必备。文档带有章节目录。 第一章 遵循最佳实践 第二章 创建可重用的对象 第三章 DOM2核心和DOM2 HTML 第四章 响应用户操作事件 第五章 动态修改样式和层叠样式表 第...

    .NET之美:.NET关键技术深入分析

    5.2.3实现IEnumerable&lt; t&gt;接口 5.3 LINQ查询 5.3.1 LINQ to Objects 5.3.2查询表达式 5.3.3延迟加载 5.3.4混合使用LINQ to Objects 5.4 LINQ查询运算符 5.4.1 返回IEnumerable&lt; t&gt; 5.4.2返回其他序列类型 ...

    Java入门1·2·3:一个老鸟的Java学习心得.PART3(共3个)

    上架时间:2010-3-30 出版日期:2010 年3月 开本:16开 其他详细信息查看:http://www.china-pub.com/196571 编辑推荐 Java编程老鸟潜心写作,奉献高效率的Java学习心得 完全站在没有编程经验读者的角度,...

    HeadFirst设计模式-大神推荐

    设计模式,开发进阶读物。 1.封装变化。 2.多用组合,少用继承 3.针对接口编程,不针对实现编程 4.为交互对象之间的松耦合设计而努力 5.................................

    IBM WebSphere Portal门户开发笔记01

    3)&lt;dnd:DNDPortletHelper/&gt; 84 (四)拖放区域 84 1)在定制主题和外表中使用拖放 85 2)拖放标记摘要 85 3)拖放 API 86 八、功能应用或问题 87 1、WCM多子站区指向指定子站区配置 87 2、更改PORTAL默认的登录选项 ...

    Java 知识汇总(资源,工具,笔记,源码,文章,文档分类整理).zip

    面向对象基本语法,抽象类,接口,内部类等;常用类api使用;常用算法,常用加密算法;Rxjava的使用;Http客户端接口测试,Okhttp的使用,HttpClient的使用,HttpUrlConnection的使用;集合的常见用法; 线程的基本知识;反射的...

    Scala in Depth

    Scala是一种多范式的编程语言,它既支持面向对象编程,也支持函数式编程的各种特性。 本书深入探讨了Scala里几个较为复杂的领域,包括类型系统的高阶内容、隐式转换、特质的组合技巧、集合、Actor、函数式编程的范畴...

Global site tag (gtag.js) - Google Analytics