Java并发编程学习——对象的组合

本文介绍了设计线程安全类的基本原则,包括确定对象状态、分析依赖状态的操作和所有权等。同时探讨了实现同步的多种方法,如对象封闭、委托线程安全性给底层状态变量以及现有类的封装。

对象的组合

主要介绍一些组合模式,使得一个类更容易成为线程安全的类,在维护这些类时不会无意中破坏类的安全性保证。

1.设计线程安全的类

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

  • 找出构成对象状态的所有变量;
  • 找出约束状态变量的不变性条件;
  • 建立对象状态的并发访问管理策略。

对象的状态

  • 如果所有的域都是基本类型,则这些域构成对象的全部状态;
  • 如果包含其他对象,该对象的状态将包括被引用对象的域。

同步策略规定了如何将不变性条件、线程封闭和加锁机制结合起来以维护线程的安全性,并且规定了哪些变量由哪些锁来保护。

1.1 收集同步需求

**尽量多的使用final域。**final类型的域使用的越多,状态空间就越小,越能简化对象可能状态的分析过程。

单个或多个域上,某一个操作如果存在的无效的状态转换,需要对该操作进行同步。无效的状态转换包括不满足:

  • 不变性条件,例如:int变量超出最大最小值。
  • 后验条件,例如:counter当前值是17,那么下一个操作结束一定是18.

如果某个操作存在无效的状态转换,那么该操作必须是原子的,即需要同步。

1.2 分析依赖状态的操作

某些方法包含一些先验条件才能执行,例如:不能够从空队列中删除一个值。单线程程序中如果遇到无法满足先验条件的情况可以直接返回失败,但是并发程序中先验条件可能因为其他线程的执行而变成真,因此要一直等待先验条件为真再执行。

1.3 分析状态的所有权

所有权在Java中只是一个设计中的要素,在语言层面没有明显的变现。所有权意味着控制权,如果发布了某个可变对象的引用,则意味着共享控制权。在定义哪些变量构成对象的状态时,只考虑对象拥有的数据。

2.同步的实现

同步的实现手段主要有三个:
* 对象封闭
* 委托线程安全性给底层状态变量
* 现有类的封装

2.1 对象封闭

当一个对象封闭在另一个对象中时,由于被访问的对象所有代码路径都是已知的,因此更易于对代码进行分析

被封闭的作用域可以是:

  • 一个实例中:作为一个私有成员
  • 某个作用域中:作为局部变量
  • 线程里:将对象从一个方法传递到另一个方法
通过封闭机制确保线程安全
@GuardedBy("this") 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也是一个线程安全的类,否则一样会线程不安全。也需要注意,不应该让一个本该封闭的对象逸出作用域。

Java中很多容器比如ArrayList和HashMap不是线程安全的类,但类库通过Collections.synchronizedList及类似方法产生一个容器的“装饰器”,使其访问变成了线程安全的。

2.1.1 Java监视器模式

遵循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;
    }
}

Counter中封装了一个变量value,对该变量的所有访问都需要通过Counter的方法执行,这些方法都是同步的。

Java监视器仅仅是一种编码约定。

私有私有锁保护状态
public class PrivateLock {
    private final Object myLock = new Object();
    @GuardedBy("myLock") Widget widget;

    void someMethod() {
        synchronized (myLock) {
            // Access or modify the state of widget
        }
    }
}

实现过程中,使用私有锁而不是对象的内置锁可以避免让客户代码错误的得到锁。

2.2 线程安全性委托

如果一个类是由多个独立线程安全的状态变量组成,所有操作中都不会破坏已有的限制条件,那么可以将线程安全性委托给底层的状态变量去做。如果某个类含有复合操作,即多个状态变量之间相互影响,仅仅委托给底层的状态变量去做就不足以保证线程安全性,需要在该类中加锁以保证线程安全性。

发布底层的状态变量 如果一个状态变量是线程安全的,并且没有任何不变性条件约束他的值,也不存在任何不允许的状态转换,那么就可以安全的发布这个变量。例如Counter,value需要是正值(约束条件)且必须递增(如果发布客户可以减少,存在不允许的状态转换),因此不能安全的发布Counter中的value状态。

public class VisualComponent {
    private final List<KeyListener> keyListeners
            = new CopyOnWriteArrayList<KeyListener>();
    private final List<MouseListener> mouseListeners
            = new CopyOnWriteArrayList<MouseListener>();

    public void addKeyListener(KeyListener listener) {
        keyListeners.add(listener);
    }

    public void addMouseListener(MouseListener listener) {
        mouseListeners.add(listener);
    }

    public void removeKeyListener(KeyListener listener) {
        keyListeners.remove(listener);
    }

    public void removeMouseListener(MouseListener listener) {
        mouseListeners.remove(listener);
    }
}

将VisualComponent里面的keyListeners和mouseListeners发不出去是安全的。因为没有在其监听器列表的合法状态上施加任何约束,这些域发布不会破坏线程安全。

2.3 给现有的类中增加功能

给现有的类增加新的功能并保持线程安全性有两种方式。

  • 客户端加锁
    • 将加锁代码分布在多个类中,破坏同步策略的封装性。
  • 组合

以给一个list增加putIfAbsent功能为例:

客户端加锁
class GoodListHelper <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 ImprovedList<T> implements List<T> {
    private final List<T> list;

    /**
     * PRE: list argument is thread-safe.
     */
    public ImprovedList(List<T> list) { this.list = list; }

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

    // Plain vanilla delegation for List methods.
    // Mutative methods must be synchronized to ensure atomicity of putIfAbsent.
    ...
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值