• Java并发编程实战笔记—— 并发编程3


    1、实例封闭

    class personset{
        private final Set<Person> myset = new HashSet<Person>();
        
        public void addPersom(Person p){
            myset.add(p);
        }
        
        public boolean containPerson(Person p){
            return myset.contains(p);
        }
    }

      这个类的状态是由HashSet来进行管理的,这里的myset是私有的且并不会逸出,因此HashSer被封闭在personset中,所以如果不对myset进行访问那这个类就是线程安全的,但是由于HashSet并不是线程安全的,所以其add和contains方法都不是线程安全的,所以需要加上锁。

    class personset{
        private final Set<Person> myset = new HashSet<Person>();
    
        public synchronized void addPersom(Person p){
            myset.add(p);
        }
    
        public synchronized boolean containPerson(Person p){
            return myset.contains(p);
        }
    }

      这样personset的状态完全由它的内置锁保护着,因而它就是一个线程安全的类。

      这个例子中并未对Person的线程安全性进行假设,如果Person类是可变的,那么从personset中获得Person对象时还需要额外的同步

      示例:车辆追踪

    class MutablePoint{
        private int x;
        private int y;
        public MutablePoint(){
            this.x = 0;
            this.y = 0;
        }
        public MutablePoint(MutablePoint m){
            this.x = m.getX();
            this.y = m.getY();
        }
        public synchronized int getX() {
            return x;
        }
    
        public synchronized void setX(int x) {
            this.x = x;
        }
    
        public synchronized int getY() {
            return y;
        }
    
        public synchronized void setY(int y) {
            this.y = y;
        }
    }  

      MutablePoint是一个记录车辆坐标的类  

    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(){
            return deepcopy(locations);
        }
        public synchronized MutablePoint getLocation(String id)
        {
            //获取要返回的数据
            MutablePoint loc = locations.get(id);
            //创建一个新的对象返回
            return loc == null ? null : new MutablePoint(loc);
        }
    
        private 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)));
            }
            //返回的是一个不可修改的Map
            return Collections.unmodifiableMap(result);
        }
    }

      MonitorVehicleTracker是一个线程安全的追踪器,它所包含的Map对象和可变的MutablePoint都是对外未曾发布的当需要返回车辆的位置的时候,通过MutablePoint的构造函数来复制新的值。

    2、线程安全性的委托

      还可以创建一个不可变的Point类来替换MutablePoint,然后构造一个委托给线程安全的车辆追踪器,我们可以先把数据存储在一个线程安全的ConcurrentHashMap类中。

    class Point{
        public final int x, y;
        public Point(int x,int y){
            this.x = x;
            this.y = y;
        }
    }

      这个Point是不可变的,因此它是线程安全,因为不可变的值可以自由的分享和发布,所以在返回location的时候不需要再复制。

    public class MonitorVehicleTracker {
        private final ConcurrentHashMapConcurrentHashMap<String,Point> locations;
        private final Map<String, Point> unmodifiableMap;
    public MonitorVehicleTracker(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 unmodifiableMap.get(id); }
    }

      这里没有使用明显的同步,所有对状态的访问都由ConcurrentHashMap来管理,创建对象的构造器中先讲数据存储在ConcurrentHashMap类型的对象中,再调用Collections的unmodifibleMap方法将数据传给unmodifiableMap,这个方法返回指定映射的不可修改视图以保证线程安全,这样想要修改unmodifiableMap中的数据(想要修改这个对象的状态),只能通过构造器新建一个对象了。

    3、当委托失效时

    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( "can't set lower to " + i + " > upper "); } lower.set(i); } public void setUpper(int i){
         //这里是不安全的
    if(i < lower.get()){ throw new IllegalArgumentException( "can't set lower to " + i + " > upper "); } upper.set(i); } public boolean isInrange(int i ){ return (i >= lower.get() && i <= upper.get()); } }

      这里的NumberRange并不是线程安全的,setLower和setUpper都是先检查后执行的操作。这里会出现的问题就是如果线程A想要修改了upper为4,但是并没有进行到set这一行,线程B想要修改lower为5是可以通过检查的。结果可能会变成(5,4)。

      结论就是如果某个类含有复合操作,就像上面这样,仅靠委托并不足以实现线程安全性,在这种情况下,这个类必须提供自己的加锁机制保证这些复合操作都是原子操作,除非整个复合操作都可以委托给状态变量。

    4、 客户端加锁机制

    class ListHelps<E>{
        public List<E> list = Collections.synchronizedList(new ArrayList<E>());
        public synchronized boolean putIfAbsent(E x){
            boolean absent = !list.contains(x);
            if(absent)
                list.add(x);
            return absent;
        }
    }

      这里实现的是如果list中没有x则添加x。这里的锁仅仅只是锁住了“如果没有则添加”这个操作,所以如果线程A进行了这个操作,其他线程并不能执行“如果没有则添加”这个操作。但是如果线程A在“其他线程调用另外的方法添加了x”之前已经执行完验证x的存在。总的来说就是putIfAbsent相对于其他list的操作并不是原子性的。

    class ListHelps<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;
            }
        }
    }

      上面的代码给出了如何使putIfAbsent相对于其他list的操作变得是原子性的。

    5、组合

    class ImprovedList<T> implements List<T>{
        private List<T> list;
    
        public  ImprovedList(List<T> list){
            this.list = list;
        }
        public synchronized  boolean putIfAbsent(T x){
            boolean absent = !list.contains(x);
            if (absent)
                list.add(x);
            return absent;
        }
    }

      这样的组合将存储数据的list,也就是真正代表类的状态的list变量封闭在了ImprovedList中,然后putIfAbsent是一个原子性的操作,这样某线程在执行putIfAbsent的时候其他线程也无法修改list的值。

    6、总结

      第一个例子实例封闭,将代表对象状态的变量 myset定义为final类型的,然后把使用这个变量的方法都加上锁,这样就能保证多线程程序使用这个类的时候的原子性。

      第二个记录车辆坐标的类中,记录对象状态的变量的初始化是通过复制得到的,且得到的是一个不可修改的map,每次返回数据的时候是通过新建一个对象返回的,实现了把map变量(代表对象状态的变量)封闭在了对象中。而且在初始化和返回数据操作时加上了内置锁,实现了线程安全

      第三个也是记录车辆坐标的,这里使用了线程安全的ConcurrentHashMap,真正代表对象状态的map变量获取数据也是从这个ConcurrentHashMap中获得,而从外界获取数据则是ConcurrentHashMap,从而实现了线程安全。

      第四个例子中代表对象状态的两个变量使用AtomicInteger,这里只实现了数据读写的线程安全,但是在逻辑上数据的使用,也就是先检查后执行的操作是根据数据的内容进行的,而且这里的setLower和setUpper两个操作的结果还是互相影响的,所以如果想要加锁,也需要把这两个操作加在一块。

      第五个和第六个想要解决的问题是一样的,第五个把关于list的操作加上锁实现线程安全,第六个把list封闭在对象中,实现每次进行putIfAbsent操作的时候其他线程无法改变list的内容

  • 相关阅读:
    算法:合并排序(Merge Sort)
    安全:Web 安全学习笔记
    算法:冒泡排序(Bubble Sort)、插入排序(Insertion Sort)和选择排序(Selection Sort)总结
    算法:四种冒泡排序(Bubble Sort)实现
    算法:阶乘的五种算法
    算法:递归知识文章汇总
    算法:插入排序(Insertion Sort)
    .NET:线程本地存储、调用上下文、逻辑调用上下文
    算法:Rate of Growth
    企业应用:一个够用的、通用的状态机(管理实体的业务状态)
  • 原文地址:https://www.cnblogs.com/xxbbtt/p/7879543.html
Copyright © 2020-2023  润新知