`

第4章 对象的组合

 
阅读更多
4.1 设计线程安全的类
在线程安全的程序中,虽然可以将程序的所有状态都保存在公有状态域中,但与那些将状态封装起来的程序相比,这些程序的线程安全性更难以得到验证,并且在修改时也更难以始终确保其线程安全性。通过使用封装技术,可以使得在不对整个程序进行分析的情况下就可以判断一个类是否是线程安全的。

在设计线程安全类的过程中,需要包含以下三个基本要素:

1.找出构成对象状态的所有变量

2.找出约束状态变量的不变性条件

3.建立对象状态的并发访问管理策略

 

4.1.2 依赖状态的操作

如果在某个操作中含有基于状态的先验条件,那么这个操作就称为 依赖状态 的操作。

 

在Java中,等待某个条件为真的各种内置机制(包括等待和通知等机制)都与内置加锁机制紧密相连,要想正确地使用它们并不容易。要想实现某个等待先验条件为真时才执行的操作,一种更简单的方法是通过现有库中的类(例如阻塞队列[Blocking Queue、Semphore])来实现依赖状态的行为。

 
4.1.3 状态的所有权
在定义哪些变量将构成对象的状态时,只考虑对象拥有的数据。所有权在Java中并没有得到充分的体现,而是属于类设计中的一个要素。如果分配并填充了一个HashMap对象,那么就相当于创建了多个对象:HashMap对象,在HashMap对象中包含的多个对象,以及在Map.Entry中可能包含的内部对象。HashMap对象的逻辑状态包括所有的Map.Entry对象以及内部对象,即使这些对象都是一些独立的对象。
许多情况下,所有权与封装性总是相互关联的:对象封装它拥有的状态,反之也成立,即对它封装的状态拥有所有权。状态变量的所有者将决定采用何种加锁协议来位置变量状态的完整性。所有权意味着控制权。然而,如果发布了某个可变对象的引用,那么就不再拥有独占控制权,最多是"共享控制权"。对于从构造函数或者从方法中传递进来的对象,类通常并不拥有这些对象,除非这些方法是被专门设计为转移传递进来的对象所有权(例如,同步容器封装器的工厂方法)。
容器类通常表现出一种"所有权分离"的形式,其中容器类拥有自身的状态,而客户代码则拥有容器中各个对象的状态。Servlet框架中的ServletContext就是其中一个示例。ServletContext为Servlet提供了类似于Map形式的对象容器服务,在ServletContext中可以通过名称来注册(setAttribute)或获取(getAttribute)应用程序对象。由Servlet容器实现的ServletContext对象必须是线程安全的,因为它肯定会被多个线程同时访问。当调用setAttribute和getAttribute时,Servlet不需要使用同步,但当使用保存在ServletContext中的对象时,则可能需要使用同步。这些对象由应用程序拥有,Servlet容器知识替代应用程序保管它们。与所有共享对象一样,它们必须安全地被共享。为了防止多个线程在并发访问同一个对象时产生的相互干扰,这些对象应该要么是线程安全的对象,要么是事实不可变的对手,或者由锁来保护的对象。
 
4.2 实例封闭
封装简化了线程安全类的实现过程,它提供了一种实例封闭机制(Instance Confinement)通常也简称"封闭"。当一个对象被封装到另一个对象中,能够访问被封装对象的所有代码路径都是已知的。与对象可以由整个程序访问的情况相比,更易于对代码进行分析。通过将封闭机制与合适的加锁策略结合起来,可以确保以线程安全的方式来使用费线程安全的对象。
将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁。
4.2.1 Java监视器模式
遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护。
在许多类中都使用了Java监视器模式,例如Vector和Hashtable。Java监视器模式的主要优势在于它的简单性。
public final class Counter{
     @GuardedBy("this") private long value=0;
    public synchronized long getValue(){
        return value;
     }
    public synchronized long increment(){
        if(value == Long.MAX_VALUE) throw new IllegalStateException("counter overflow");
          return ++value;
     }
}

  

使用私有锁对象而不是对象的内置锁(或任何其他科通过公有方式访问的锁),有许多优点。私有的锁对象可以将锁封装起来,使客户代码无法得到锁,但客户代码可以通过公有方法来访问锁,以便(正确或不正确地)参与到它的同步策略中。如果客户代码错误的获得了另一个对象的锁,那么可能会产生活跃性问题。此外,要想验证某个公有访问的锁在程序中是否被正确的使用,则需要检查整个程序,而不是单个的类。
public class PrivateLock {
    private final Object myLock = new Object();
    @GuardedBy("myLock") Widget widget;
    void someMethod(){
          sychronized(myLock){
              //访问或修改Widget的状态
         }
     }
}
 
4.4 在现有的线程安全类中添加功能
4.4.1 客户端加锁机制
对于由Collections.synchronizedList封装的ArrayList,这两种方法在原始类中添加一个方法或者对类进行扩展都行不通,因为客户端代码并不知道在同步封装器工厂方法中返回的List对象的类型。第三种策略是扩展类的功能,但并不是扩展类本身,而是将扩展代码放入一个"辅助类"中。
@NotThreadSafe
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;
}
}

这种方式不能实现线程安全性。问题在于在错误的锁上进行了同步。无论使用哪一个锁来保护它的状态,可以确定的是,这个锁并不是ListHelper上的锁。ListHelper只是带来了同步的假象。要想使这个方法能正确执行,必须使List在实现客户端加锁或外部加锁时使用同一个锁。

客户端加锁是指,对于使用某个对象X的客户端代码,使用X本身用于保护其状态的的锁来保护这段客户端代码。要使用客户端加锁,就必须知道对象X使用的是哪一个锁。

在Vector和同步封装器类的文档中指出,它通过使用Vector或封装器的内置锁来支持客户端加锁。

@ThreadSafe
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;
}
}
}

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

客户端加锁机制与扩展类机制有许多共同点,二者都是将派生类的行为与基类的实现耦合在一起。正如扩展会破坏实现的封装性,客户端加锁同样会破坏同步策略的封装性。

 
4.4.2 组合
当为现有的类添加一个原子操作时,组合是更好的方法。
@ThreadSafe
public class ImprovedList<T> implements List<T>{
public final List<E> list;
public ImprovedList<T>(List<E> list){this.list = list}
public synchronized boolean putIfAbsent(T x){
boolean contains = list.contains(x);
if(contains) list.add(x);
return !contains;
}
public synchronized void clear(){list.clear();}
// ... 按照类似的方式委托List的其他方法
}
ImprovedList通过自身的内置锁增加了一层额外的加锁。它并不关心底层的List是否是线程安全的,即使List不是线程安全的或者修改了它的加锁实现,ImprovedList也会提供一直的加锁机制来实现线程安全性。虽然额外的同步层可能导致轻微的性能损失,但与模拟另一个对象的加锁策略相比,ImprovedList更为健壮。
分享到:
评论

相关推荐

    大数据必修课 Python基础入门教程 Python自学资料课件-第4章 Python组合数据类型 共60页.pptx

    第4章 Python组合数据类型.pptx 第5章 Python正则表达式.pptx 第6章 Python函数.pptx 第7章 Python模块.pptx 第8章 Python类和对象.pptx 第9章 Python异常.pptx 第10章 Python文件操作.pptx 第11章 Python项目实战:...

    《java并发编程实战》读书笔记-第4章-对象的组合

    《java并发编程实战》读书笔记-第3章-对象的共享,脑图形式,使用xmind8制作 包括线程安全类设计、实例封闭、线程安全性委托、现有线程安全类中添加功能和文档化同步策略等内容

    大数据必修课 Python基础入门教程 Python自学资料课件-第8章 Python类和对象 共46页.pptx

    第4章 Python组合数据类型.pptx 第5章 Python正则表达式.pptx 第6章 Python函数.pptx 第7章 Python模块.pptx 第8章 Python类和对象.pptx 第9章 Python异常.pptx 第10章 Python文件操作.pptx 第11章 Python项目实战:...

    写给大家看的面向对象编程书(第三版)

    第4章 类剖析 第5章 类设计指导原则 第6章 利用对象实现设计 第7章 掌握继承和组合 第8章 框架与重用:使用接口和抽象类实现设计 第9章 构建对象 第10章 用UML创建对象模型 第11章 对象和可移植数据:XML 第...

    组合数学的算法与程序设计

    第四章 容斥原理 4.1 容斥原理的两种形式 4.2 容斥原理的一般形式 4.3 容斥原理的应用 第五章 母函数 5.1 母函数的引出 5.2 普通母函数 5.3 指数母函数 习题五 第六章 递归关系 6.1 递归关系的定义和建立 6.2 ...

    C++编程思想之第一卷

    第4章 数据抽象 第5章 隐藏实现 第6章 初始化与清除 第7章 函数重载与默认参数 第8章 常量 第9章 内联函数 第10章 名字控制 第11章 引用和拷贝构造函数 第12章 运算符重载 第13章 动态对象创建 第14章 继承和组合 第...

    Java并发编程实战2019.zip

    Java并发编程实战,第1章 简介,第2章 线程安全性 第3章 对象的共享 第4章 对象的组合 第5章 基础构建模块 第6章 任务执行 第7章 取消与关闭 第8章 线程池的使用 第9章 图形用户界面应用程序 第10章 避免...

    C++编程思想.rar

    第1章对象的演化 第2章数据抽象 第3章隐藏实现 第4章初始化与清除 第5章函数重载与缺省参数 第6章输入输出流介绍 第7章常量 第8章内联函数 第9章命名控制 第10章引用和拷贝构造函数 第11章运算符重载 第12章动态对象...

    组合数学及其算法

    第四章 鸽巢原理 4.1 鸽巢原理 4. 2 鸽巢原理的推广形式 4. 3 ramsey数 4.4 ramsey数的性质 4.5 ramsey定理 习 题 第五章 母函数 5.1 母函数概念 5.2 幂级数型母函数 5.3 整数的拆分 5.4 ferrers...

    Python3基础教程第4章.pptx

    第4章组合数据类型 本章主要内容: 集合 列表 元组 字典 迭代和列表解析 Python3基础教程第4章全文共118页,当前为第2页。 4.1 集合 集合(set)是Python 2.4引入的一种类型。 集合常量用大括号表示,例如,{1,2,3}...

    大数据必修课 Python基础入门教程 Python自学资料课件-第9章 Python异常 共24页.pptx

    第4章 Python组合数据类型.pptx 第5章 Python正则表达式.pptx 第6章 Python函数.pptx 第7章 Python模块.pptx 第8章 Python类和对象.pptx 第9章 Python异常.pptx 第10章 Python文件操作.pptx 第11章 Python项目实战:...

    大数据必修课 Python基础入门教程 Python自学资料课件-第6章 Python函数 共49页.pptx

    第4章 Python组合数据类型.pptx 第5章 Python正则表达式.pptx 第6章 Python函数.pptx 第7章 Python模块.pptx 第8章 Python类和对象.pptx 第9章 Python异常.pptx 第10章 Python文件操作.pptx 第11章 Python项目实战:...

    大数据必修课 Python基础入门教程 Python自学资料课件-第1章 Python3概述 共51页.pptx

    第4章 Python组合数据类型.pptx 第5章 Python正则表达式.pptx 第6章 Python函数.pptx 第7章 Python模块.pptx 第8章 Python类和对象.pptx 第9章 Python异常.pptx 第10章 Python文件操作.pptx 第11章 Python项目实战:...

    清华大学精品Python学习PPT课件-第7章 Python模块.pptx

    清华大学精品Python学习PPT课件-第4章 Python组合数据类型.pptx 清华大学精品Python学习PPT课件-第5章 Python正则表达式.pptx 清华大学精品Python学习PPT课件-第6章 Python函数.pptx 清华大学精品Python学习PPT课件-...

    Thinking in C++ 中文版(高清版含完整书签)

    第4章 初始化与清除 第5章 函数重载与缺省参数 第6章 输入输出流介绍 第7章 常量 第8章 内联函数 第9章 命名控制 第10章 引用和拷贝构造函数 第11章 运算符重载 第12章 动态对象创建 第13章 继承和组合 第14章 多态...

    刘伟1..25章设计模式大集合

    01_第1章_统一建模语言基础知识.ppt 第11章_桥接模式.ppt ...第4章_简单工厂模式.ppt 第5章_工厂方法模式.ppt 第6章_抽象工厂模式.ppt 第7章_建造者模式.ppt 第8章_原型模式.ppt 第9章_单例模式.ppt

    C#设计模式 C# Design Patterns:A Tutorial

    第4章 在C#中使用类和对象 第5章 继承 · · · · · · (更多) 第一部分 C#面向对象程序设计 第1章 设计模式概述 第2章 C#语言的语法 第3章 用C#编写Windows程序 第4章 在C#中使用类和对象 第5章 继承 第6章 UML...

    计算机应用基础第4章-中文Excel-.pptx

    第四章 Excel 2010的使用 4.6 数据清单 4.7 工作表的打印和超链接 4.8 工作簿和工作表的保护和隐藏 4.9 小结 4.10 习题 2023/5/31 4 计算机应用基础第4章-中文Excel-全文共74页,当前为第4页。 4.1 Excel 2010概述 ...

    C++ 编程思想(中文完整版)

    第4章 初始化与清除 第5章 函数重载与缺省参数 第6章 输入输出流介绍 第7章 常 量 第8章 内 联 函 数 第9章 命 名 控 制 第10章 引用和拷贝构造函数 第11章 运算符重载 第12章 动态对象创建 第13章 继承和组合 第14...

    C++编程思想(第2版)_第1卷_标准C++引导

    第4章 数据抽象 第5章 隐藏实现 第6章 初始化与清除 第7章 函数重载与默认参数 第8章 常量 第9章 内联函数 第10章 名字控制 第11章 引用和拷贝构造函数 第12章 运算符重载 第13章 动态对象创建 第14章 继承和组合 第...

Global site tag (gtag.js) - Google Analytics