版权声明:此文章转载自ITeye。 如需转载请联系听云College团队成员阮小乙,邮箱:ruanqy#tingyun.com
我们知道,面向对象的三大特征之一是封装。封装是指将一个对象A放到另一个对象B的内部,A作为B的成员变量,要想访问对象A只能通过对象B提供的方法。当把一个对象进行了封装之后,我们更容易分析客户代码对该对象的访问方式。
如果将对象封装和加锁机制联合起来,就可以确保以线程安全的方式来使用非线程安全的对象。比如下面代码:
Java代码
@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); } }
或者
Java代码
@ThreadSafe public class StringSet{ private final Set<String> mySet=new HashSet<String>(); public void addString(String str){ synchronized(this){ mySet.add(str); } } public synchronized boolean containsString(String str){ boolean result=false; synchronized(this){ result=mySet.contains(str); } return result; } }
或者
Java代码
@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 synchronized boolean containsString(String str){ boolean result=false; synchronized(myLock){ result=mySet.contains(str); } return result; } }
上面的三段代码是同样的功能,他们之间略有不同,第一种和第二种是对象锁的方式,第三种是私有锁的方式,但最终都达到了线程安全的目的。以第一个实现为例子进行说明,虽然HashSet是非线程安全的类,但是通过将他封装在StringSet的内部,并且使用合适的加锁策略,可以做到以线程安全的方式访问非线程安全的对象。仔细研究上面的代码,可以发现,类StringSet将可变对象HashSet封装起来,并有自己的内置锁来保护。像这种模式的线程安全方法叫他“Java监视器模式”。
实现Java监视器模式有两个步骤:
1 将所有的可变对象都封装起来
2 将可变对象用内置锁类保护起来
开发线程安全类还有一种基于委托的方式,即将自己的线程安全性寄托在别的类上。比如在《java并发编程实战》中的一个例子--车辆追踪器:
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修改如下:
Java代码
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监视器模式实现一下:
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监视器模式的是返回静态快照,基于委托的方式使用动态的视图。