您的位置:首页 > 编程语言 > Java开发

java并发实践------组合对象

2014-11-21 14:22 197 查看
首先阐述相关的概念术语。

1、组合对象:

     由多个线程安全的状态变量组合而成。

2、同步策略:

    包含两个关键点:不变约束和后验条件。

    不变约束:指的是对变量(或者叫做域)在状态转换过程中的约束,这个约束伴随整个变量的生命周期。这个跟业务有很强的关联性。比如正数,它的不变约束就是必须大于0.

    后验条件:指变量在进行状态转换后,其要满足业务上的约束。

组合对象这一章节,主要讲的就是如何设计线程安全的对象。

设计线程安全的对象包含三个要素:

  (1)确定对象状态是由哪些变量构成;

  (2)确定限制对象的不变约束。

  (3)制定一个管理并发访问对象的策略。

构建线程安全的类包含如下几种方式:

  1、实例限制;

  2、委托线程安全;

  3、扩展已有的线程安全类,增添新的同步方法;

  1.1、什么是实例限制?

        用比较通俗易懂的话来说:A类封装B对象(可以包含多个,B不是线程安全的),通过使用JAVA监视器模式,来达到对B类操作的线程安全。如下面代码所示:

@ThreadSafe
public class PersonSet {
@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);
}
}
     HashSet本不是线程安全的,通过上述的实例封装,达到了对HashSet的线程安全访问。但是要注意:该线程安全并不会扩展到Person对象。

2.1 如何达到委托线程安全?

    委托顾名思义,就是把职责委托给别人,在这里,委托就是把线程安全的职责委托给类内部封装的对象(或者说是变量),由定义可以伸展出:该类封装的内部变量是线程安全的,不同于上面的实例限制封装的对象(不是线程安全)。来看一下实例代码:@ThreadSafe
public class DelegatingVehicleTracker {
private final ConcurrentMap<String, Point> locations;
private final Map<String, Point> unmodifiableMap;
public DelegatingVehicleTracker(Map<String, Point> points) {
locations = new ConcurrentHashMap<String, Point>(points);
unmodifiableMap = Collections.unmodifiableMap(locations);
}
public Map<String, Point> getLocations() {
return unmodifiableMap;
}
public Point getLocation(String id) {
return locations.get(id);
}
public void setLocation(String id, int x, int y) {
if (locations.replace(id, new Point(x, y)) == null)
throw new IllegalArgumentException(
"invalid vehicle name: " + id);
}
}DelegatingVehicleTracker把线程安全的职责委托给了两个封装的内部对象,该两个对象都是线程安全的,ConcurrentMap是线程安全的,umodifiableMap是不可更改的,其被final关键字修饰,故其也是线程安全的。
注意一点:采用委托线程安全策略,可能会违背后验条件,如下代码所示:

public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}

NumberRange把线程安全委托给了两个封装的对象,但是该类却不是线程安全的,其在多线程环境下,可能会导致 upper<lower,违反不变约束。假设某一个时刻NumberRange的值域是(0,10),一个线程调用setUpper(5),同时另一个线程调用setLowwer(6),那么最终会产生(6, 5)的值域。这肯定违反不变约束了。这个时候需要用syn...关键字来修饰两个public方法,回归到实例封装方式。
3.1 扩展已有的线程安全类

    这个对面向对象开发人员来说应该很easy。直接看一个例子,

@ThreadSafe
public class BetterVector<E> extends Vector<E> {
public synchronized boolean putIfAbsent(E x) {
boolean absent = !contains(x);
if (absent)
add(x);
return absent;
}
}

但是在使用该方式时,一定要注意加锁要统一,否则会出现非线程安全。看如下的例子:
@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;
}
}

该类不是线程安全的,原因是锁不一致。putIfAbsent方法的锁是ListHelper的intrinsic lock,而访问list,用到的却是list的intrinsic lock。
@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;
}
}
}上述代码是线程安全的,其用的是统一的list的instrinsic lock。

后话:

   在进行对象设计阶段,设计者一定要标识类的线程安全性,否则用于使用者,以及维护者将是大的灾难。

   对于类的用户而言,开发者要编写类线程安全担保文档,也就是这个类到底是不是线程安全的。

   对于类的维护者而言,开发者要编写同步策略文档,也就是这个类的不变约束是什么?后验条件是什么?采用了何种方式实现的类的线程安全性。都要有一个完善的说明。这样无论对于类的使用者,还是维护者,还是开发者都是受益的。所以我在这里倡议:关键点,一定要追加文档说明说。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: