JAVA并发-开发线程安全类

我们知道,面向对象的三大特征之一是封装。封装是指将一个对象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监视器模式的是返回静态快照,基于委托的方式使用动态的视图。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值