我们知道,面向对象的三大特征之一是封装。封装是指将一个对象A放到另一个对象B的内部,A作为B的成员变量,要想访问对象A只能通过对象B提供的方法。当把一个对象进行了封装之后,我们更容易分析客户代码对该对象的访问方式。
如果将对象封装和加锁机制联合起来,就可以确保以线程安全的方式来使用非线程安全的对象。比如下面代码:
@ThreadSafe
public class StringSet{
private final Set<String> mySet=new HashSet<String>();
public synchronized void addString(String str){
mySet.add(str);
}
public synchronized boolean containsString(String str){
return mySet.contains(str);
}
}
或者
@ThreadSafe
public class StringSet{
private final Set<String> mySet=new HashSet<String>();
public void addString(String str){
synchronized(this){
mySet.add(str);
}
}
public boolean containsString(String str){
boolean result=false;
synchronized(this){
result=mySet.contains(str);
}
return result;
}
}
或者
@ThreadSafe
public class StringSet{
ptivate Object myLock=new Object();
private final Set<String> mySet=new HashSet<String>();
public void addString(String str){
synchronized(myLock){
mySet.add(str);
}
}
public boolean containsString(String str){
boolean result=false;
synchronized(myLock){
result=mySet.contains(str);
}
return result;
}
}
上面的三段代码是同样的功能,他们之间略有不同,第一种和第二种是对象锁的方式,第三种是私有锁的方式,但最终都达到了线程安全的目的。以第一个实现为例子进行说明,虽然HashSet是非线程安全的类,但是通过将他封装在StringSet的内部,并且使用合适的加锁策略,可以做到以线程安全的方式访问非线程安全的对象。仔细研究上面的代码,可以发现,类StringSet将可变对象HashSet封装起来,并有自己的内置锁来保护。像这种模式的线程安全方法叫他
“JAVA监视器模式”。
实现JAVA监视器模式有两个步骤:
1 将所有的可变对象都封装起来
2 将对可变对象的访问用锁保护起来
开发线程安全类还有一种基于委托的方式,即将自己的线程安全性寄托在别的类上。比如在《java并发编程实战》中的一个例子--车辆追踪器:
public class Point{
public final int x,y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
}
@ThreadSafe
public class CarTracker{
private final ConcurrentMap<String,Point> locations;
private final Map<String,Point> unmodifiableMap;
public CarTracker(Map<String,Point> points){
locations=new ConcurrentMap<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)
System.out.print("id not exists");
}
}
上面的代码中CarTracker将自己的安全性委托给了ConcurrentMap,这个类是安全的,所以CarTracker是安全的。这里unmodifiableMap的存在设计的很好,如果getLOcations()方法放回的是locations,当然也是线程安全的,那样的话程序的性能会由于locations对并发的限制而受到影响(getLocations()和getLocation()不能同时执行)。而设计成unmodifiableMap即保证了安全性,有提升了性能。还有一点:Point这个类对象是不可变的,这就保证了getLocation()返回的Point引用的值不会被更改,避免了逸出而保证了安全性。如果将Point修改如下:
public class Point{
public int x,y;
public Point(int x,int y){
this.x=x;
this.y=y;
}
public synchronized int[] get(){
return int[] (x,y);
}
public synchronized set(int x,int y){
this.x=x;
this.y=y;
}
}
上面的Point类的设计也能够保证线程安全性,与之前的相比,这个类的设计允许客户代码修改Point的值,具体采用那种设计取决于具体的场景。
上面就是使用 基于委托的方式实现线程安全性。
将上面的车辆追踪器用java监视器模式实现一下:
public class Point{
public fianl int x,y;
public Point(Point p){
this.x=p.x;
this.y=p.y;
}
}
public class CarTracker{
private Map<String,Point> locations;
public CarTracker(Map<String,Point> points){
locations=deepCopy(points);
}
public synchronized Map<String,Point> getLocations(){
return deepCopy(locations);
}
public synchronized Point getLocation(String id){
Point p=locations.get(id);
return (p==null)?null:new Point(p);
}
public synchronized void setLocation(String id,int x,int y){
Point p=locations.get(id);
if(p==null)
System.out.print("id not exists");
p.x=x;
p.y=y;
}
public static Map<String,Point> deepCopy(Map<String,Point> m){
Map<String,Point> result=new HashMap<String,Point>();
for(String id:m.keySet()){
result.put(id,new Point(m.get(id)));
}
return Collections.unmodifiable(result);
}
}
比较 基于java监视器模式 和 基于委托的模式 ,可以看出,基于java监视器模式的是返回静态快照,基于委托的方式使用动态的视图。