对象的组合(第四章)

简介:

对象的组合

    在设计线程安全类的过程中,需要包含以下三个基本要素:
    1. 找出构成对象状态的所有变量
    2. 找出约束状态变量的不变性条件
    3. 建立对象状态的并发访问管理策略
  • 当对象的下一个状态需要依赖当前状态时,这个操作就必须是一个复合操作。如有一个Counter类,当前状态为17,那么下一个状态只能是18
  • 相关变量的读取和更新必须在单个原子操作中进行
  • 如果某个操作中包含有基于状态的先验条件,那么这个操作就称为依赖状态的操作。如删除一个队列里的元素,这个队列当前必须是“非空”的
  • 封闭机制更易于构造线程安全的类

1.Java监视器模式

遵循Java监视器模式的对象会把对象的所有可变状态都封装起来,并由对象自己的内置锁来保护
以下代码是对象使用私有锁的例子:

public class PrivateLock {
    private final Object myLock = new Object();
     public void doSomething() {
        synchronized(myLock) {...}
    }   
}

2.线程安全性的委托

委托是创建线程安全类的一个有效手段:只需让现有的线程安全类管理所有的状态即可。
通过多个线程安全类组合而成的类,其线程安全性视情况而定。

  1. 对象中有单个线程安全对象是线程安全的
  2. 如果一个类是由多个独立且线程安全的状态变量组成的,并且在所有的操作中都不包含无效状态转换,那么可以将线程安全性委托给底层的状态变量
  3. 如果类中多个变量之间存在着一定的不变性条件,那么这个类就不是线程安全的,需要采取同步机制
  4. 如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在不允许的状态转换,那么就可以安全地发布这个变量

3.在现有的线程安全类中添加功能

要在现有的类中添加线程安全操作,有两种方法:

  1. 修改原始类的代码
  2. 扩展这个类。一下代码扩展了Vector
public class BetterVector<E> extends Vector<E> {
    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !contains(x);
        if (absent) {add(x);}
        return absent;
    }
}

扩展的方法比直接修改源代码要脆弱,因为同步策略实现被分布到了多个单独维护的源代码文件中。如果底层的类改变了同步策略并选择了不同的锁来保护它的状态变量,那么子类会被破坏。

  1. 客户端加锁机制
    客户端加锁机制是指,对于使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户代码。要使用客户端加锁机制,必须知道对象X使用的是哪一个锁。如下代码中,第一个实现版本使用了不同的锁,所以其并不是原子的。
public class ListHelper<E> {
    public List<E> list = Collections.synchronizedList(new ArrayList<E>());
    //verson 1
    public synchronized boolean putIfAbsent(E x) {
        boolean absent = !list.contains(x);
        if (absent) list.add(x);
        return absent;
    }
    //verson 2
    public boolean putIfAbsent(E x) {
        synchronized(list) {//使用list自己的锁
            boolean absent = !list.contains(x);
            if (absent) list.add(x);
            return absent;
        }
    }
}
  1. 组合
    为现有类添加一个原子操作时,更好的方法是:组合(现有类作为对象的私有final域)

4.将同步策略文档化

在文档中说明客户代码需要了解的线程安全性保证,以及代码维护人员需要了解的同步策略

相关文章
|
6月前
|
设计模式
建模底层逻辑问题之以命令设计模式为例,要用定义法建模,如何实现
建模底层逻辑问题之以命令设计模式为例,要用定义法建模,如何实现
|
8月前
|
JavaScript 前端开发 Java
函数形状的定义方式在编程中可以有多种,具体取决于使用的编程语言和上下文。以下是几种常见的定义方式:
函数形状的定义方式在编程中可以有多种,具体取决于使用的编程语言和上下文。以下是几种常见的定义方式:
70 3
|
6月前
|
设计模式 固态存储 开发者
创建一个仅用一个函数/模块/类就可以处理这组不同事物的抽象
在软件开发中,良好的变量名和函数名能够显著提升代码的可读性和可维护性。对于变量命名,建议使用有意义且易于发音的名字(如 `currentDate` 而非 `yyyymmdstr`),对同类变量使用一致的词汇(如统一使用 `getUser`),以及使用解释性的变量名以减少理解成本(如使用常量 `MILLISECONDS_IN_A_DAY` 替代数字 `86400000`)。此外,避免不必要的上下文重复,并使用默认参数代替逻辑运算。对于函数,应限制参数数量(理想情况下不超过两个),确保每个函数只做一件事,并且函数名应清晰描述其功能。通过遵循这些原则,代码将更加简洁明了。
42 3
|
9月前
|
存储 Java 程序员
Java数组全套深入探究——基础知识阶段2、数组的定义语法
Java数组全套深入探究——基础知识阶段2、数组的定义语法
76 0
|
9月前
|
存储 传感器 机器学习/深度学习
Java数组全套深入探究——进阶知识阶段6、三维数组以及更多维度数组的概念和用法
Java数组全套深入探究——进阶知识阶段6、三维数组以及更多维度数组的概念和用法
169 0
|
9月前
|
NoSQL 算法 Redis
【Redi设计与实现】第四章:字典
【Redi设计与实现】第四章:字典
|
算法 C++ 容器
关系类算法函数
关系类算法函数
|
Java
ChatGPT告诉你Java内部类 vs. 组合的区别
ChatGPT告诉你Java内部类 vs. 组合的区别
119 0
|
前端开发
前端学习案例8-寄生组合继承1
前端学习案例8-寄生组合继承1
74 0
前端学习案例8-寄生组合继承1
|
前端开发
前端学习案例9-寄生组合继承2
前端学习案例9-寄生组合继承2
65 0
前端学习案例9-寄生组合继承2