【Java并发】JAVA并发编程实战-读书笔记5

设计线程安全类的过程应该包括下面3个基本要素

  1,确定对象状态是由哪些变量构成的

  2,确定限制状态变量的不变约束

  3,制定一个管理并发访问对象状态的策略

将数据封装在对象内部,把对数据的访问限制在对象的方法上,更易确保线程在访问数据时总能获得正确的锁。

public class PersonSer{

  private final Set mySer=new HashSet();

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

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

}
上面的例子是线程安全的,其中未对 Person 的线程安全性做任何假设,如果他是可变的,那么访问从 PersonSet 中获得的 Person 时,还需要额外的同步。为了安全的使用 Person 对象,最可靠的方法是让 Person 自身是线程安全的,对 Person 对象加锁并不十分可靠,因为他还需要所有的用户都遵守协议:访问 Person 前先获得正确的锁。

public class MonitorVehicleTracker{

  private final Map locations;

  public MonitorVehicleTracker(Map locations){
    this.locations=deepCopy(locations);
  }

  public synchronized Map getLocations(){
    MutablePoint loc=locations.get(id);
    return loc==null?null:new MutablePoint(loc);
  }

  public synchronized void setLoaction(String id,int x,int y){
    MutablePoint loc=location.get(id);
    if(loc==null){
      throw new IllegalArgumentException(“No such ID:”+id);
    }
    loc.x=x;
    loc.y=y;
  }

  private static Map deepCopy(Map m){
    Map result = new HashMap();
    for(String id:m.keySet()){
      result.put(id,new MutablePoint(m.get(id)));
    }
    return Collections.unmodifiableMap(result);
  }
}

public class MutablePoint{

  public int x,y;

  public MutablePoint(){
    x=0;
    y=0;
  }

  public MutablePoint(MutablePoint p){
    this.x=x;
    this.y=y;
  }
}
我们可以使用 Point 代替MutablePoint

public class Point{

  public final int x,y;

  public Point(int x,int y){
    this.x=x;
    this.y=y;
  }
}
由于 Point 类时不可变的,因而是线程安全的,所以我们返回 location 时不必再复制他们。

使用委托将线程安全交给ConcurrentHashMap

public class DelegatingVehicleTracker{

  private final ConcurrentMap locations;

  private final Map unmodifiableMap;

  public DelegatingVehicleTracker(Map points){
    locations=new ConcurrentHashMap(points);
    unmodifiableMap=Collections.unmodifiableMap(locations);
  }

  public Map 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);
  }
}
 
public class VisualComponent{

  private final List keyListeners=new CopyOnWriteArrayList();

  private final List mouseListeners=new CopyOnWriteArrayList();

  pulic 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);
  }

}
上面的类使用 CopyOnWriteArrayList 存储每个监听器清单。这个线程安全的 List 实现油气适合管理监听器的清单。其中,不但每个 List 是线程安全的,而且不存在哪个不变约束增加一个状态与另一个状态间耦合。但大多数组合对象不像上面的类那样简单,他们的不变约束与组件的状态变量相互联系。

public class NumberRange{

  private final AtomicInteger lower=new AtomicInteger(0);

  private final AtomicInteger upper=new AtomicInteger(0);

  public void setLower(int i){
    if(i>upper.get()){
      throw new IllegalArgumentException();
    }
    lower.set(i);
  }

  public void setUpper(int i){
    if(ilower.get()&&i<=upper.get());
  }
}

上面的类不是线程安全的,两个设置方法试图保护不变约束,但是没有适当的加锁,检查再运行时存在风险的。而且也要避免发布 lower upper ,以防止用户潜在地破坏不变约束。

如果一个类由多个彼此独立的线程安全的状态变量组成,并且类的操作不包含任何无效状态转换时,可以讲线程安全委托给这些状态变量。

添加一个新的原子操作有两种方式,一是修改原始的类。二是扩展这个类,假设这个类在设计上是可扩展的。但是并非所有的类都给子类暴露了足够多的状态以支持这种方案。因为扩展后,同步策略的实现会被分布到多个独立维护的源代码中,如果底层的类选择了不同的锁来保护她的状态变量,从而会改变他的同步策略,子类就在不知不觉中被破坏,因为他不能再用正确的锁控制对基类状态的并发访问。

public class BetterVector extends Vector{

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

上面的例子是安全的,因为Vector的同步策略已由其规约固定住,所以其子类不会有问题。

对于一个由Collections.synchronizedList封装的ArrayList,两种方法都不正确,因为客户端代码甚至不知道同步封装工厂方法返回的List对象的类型。第三个策略是扩展功能,而不是扩展类本身,并将扩展代码置入一个助手类。

public class ListHelper{

  public List list=Collections.synchronizedList(new ArrayList());

  pubic synchronized boolean putIfAbsent(E x){
    boolean absent=!list.contains(x);
    if(absent){
      list.add(x);
    }
    return absent;
  }
}
上面的例子不是线程安全的。这里的问题在于同步行为发生在错误的锁上。无论 List 使用哪个锁保护她的状态,可以确定这个锁并没用到 ListHelper 上。

为了保证这个方法正确工作,我们必须保证方法使用的锁与List用于客户端加锁与外部加锁时所用的锁时同一个锁。

public class ListHelper{

  public List list=Collections.synchronizedList(new ArrayList());

  public boolean putIfAbsent(E x){
    synchronized(list){
      boolean absent=!list.contains(x);
      if(absent){
        list.add(x);
      }
      return absent;
    }
  }
}
上面的类时线程安全的。

向已有的类中添加一个原子操作,还可以使用组合。

public class ImprovedList implements List{

  private final List list;

  public ImprovedList(List 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();
  }
}
通过内部锁, ImprovedList 引入了一个新的锁层。他并不关心底层的 List 是否线程安全,即使 List 不是线程安全的,或者会改变 ImprovedList 的锁实现, ImprovedList 都有自己兼容的锁可以提供线程安全性,虽然会有一部分性能的损失。

你可能感兴趣的:(Java并发编程实战笔记)