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

来源:互联网 发布:javaee编程技术第二版 编辑:程序博客网 时间:2024/05/16 19:19

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

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

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

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

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

public class PersonSer{  private final Set<Person> mySer=new HashSet<Person>();  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<String,MutablePoint> locations;  public MonitorVehicleTracker(Map<String,MutablePoint> locations){    this.locations=deepCopy(locations);  }  public synchronized Map<String,MutablePoint> 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<String,MutablePoint> deepCopy(Map<String,MutablePoint> m){    Map<String,MutablePoint> result = new HashMap<String,MutablePoint>();    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<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);  }}
 
public class VisualComponent{  private final List<KeyListener> keyListeners=new CopyOnWriteArrayList<KeyListener>();  private final List<MouseListener> mouseListeners=new CopyOnWriteArrayList<MouseListener>();  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(i<lower.get()){      throw new IllegalArgumentException();    }    upper.set(i);  }  public boolean isInRange(int i){    return (i>lower.get()&&i<=upper.get());  }}

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

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

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

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

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

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

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

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

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;    }  }}
上面的类时线程安全的。

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

public class ImprovedList<T> implements List<T>{  private final List<T> list;  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;  }  public synchronized void clear(){    list.clear();  }}
通过内部锁,ImprovedList引入了一个新的锁层。他并不关心底层的List是否线程安全,即使List不是线程安全的,或者会改变ImprovedList的锁实现,ImprovedList都有自己兼容的锁可以提供线程安全性,虽然会有一部分性能的损失。
0 0
原创粉丝点击