Java多线程-开发线程安全类

版权声明:此文章转载自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监视器模式的是返回静态快照,基于委托的方式使用动态的视图。

想阅读更多技术文章,请访问听云技术博客,访问听云官方网站感受更多应用性能优化魔力。

关于作者

阮小乙

人生就该充满正能量!

我要评论

评论请先登录,或注册