高并发编程:同步类容器的问题

版权声明:本文为张仕宗原创文章,允许转载,转载时请务必标明文章原始出处 。 https://blog.csdn.net/zhang5476499/article/details/83066581

同步容器类存在的问题

同步类容器都是线程安全的,但在某些场景下可能需要加锁来保护复合操作,在复合操作,如:迭代、跳转已经条件运算中,这些操作可能会表现出意外的行为,最经典的便是ConcurrentModificationException,原因是当容器迭代的过程中,被并发的修改了内容,这是由于早起迭代器设计的时候并没有考虑并发修改的原因。

开门见山,我们直接来看两个例子:

public class UseVector {
    /**
     * 遍历向量
     * @return
     */
    public void travelVector(Vector<String> list) {
        for (String str: list) {
            System.out.println(str);
        }
    }

    /**
     * 该方法的主要作用是:从数组中移除指定的元素
     * @param list
     * @param target
     * @return 抛出异常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOne(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        //抛出java.util.ConcurrentModificationException异常
        for(String str : list) {
            if(target.equals(str)) {
                list.remove(str);
            }
        }

        return list;
    }

    public static void main(String[] args) {
        //定义了一个动态数组,并向动态数组中添加3个元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();
        //遍历这个数组
        demo.travelVector(vector);
        //从这个动态数组中移除3这个元素
        demo.removeOne(vector, "3");
    }
}

该案例创建了一个动态数组Vector并向动态数组中添加3个元素,同时该类有两个方法,分别是travelVector()遍历动态数组方法和removeOne从动态数组中移除掉目标元素(target)的方法。运行上诉代码,程序抛出异常:

java.util.ConcurrentModificationException
	at java.util.Vector$Itr.checkForComodification(Vector.java:1210)
	at java.util.Vector$Itr.next(Vector.java:1163)
	at com.springchang.threadcore.test.UseVector.removeOne(UseVector.java:31)
	at com.springchang.threadcore.test.UseVector.main(UseVector.java:51)

果不其然,该代码抛出了在并发编程中常见的ConcurrentModificationException异常,并追踪上述代码得知是removeOne方法中的list.remove(str);这一行抛出的异常。咱暂时先不管为啥抛出的异常,先想想有没有其他代替方案解决我们的需求:遍历我们的数组,当数组元素等于目标值时将其移除。上述循环数组用的是增强for循环的方法,我们不妨换成另一种方式,用Iterator迭代器试一试。

public class UseVector {
    /**
     * 通过迭起器方法移除一个元素
     * @param list
     * @param target
     * @return  抛出异常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOneByIt(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        Iterator<String> it = list.iterator();
        while(it.hasNext()) {
            String str = it.next();
            if(target.equals(str)) {
                list.remove(str);
            }
        }
        return list;
    }

    public static void main(String[] args) {
        //定义了一个动态数组,并向动态数组中添加3个元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        //从这个动态数组中移除3这个元素
        demo.removeOneByIt(vector, "3");
    }
}

悲伤的是Iterator迭代器并没有解决我们的问题,同样的Iterator也抛出了ConcurrentModificationException异常。在JDK 1.5之前有没有解决的方案呢?有的,请看以下代码:

public class UseVector {
    /**
     * 安全的从数组中移除元素的方法
     * @param list
     * @param target
     * @return  不会抛出抛出异常java.util.ConcurrentModificationException
     */
    public Collection<String> removeOneBySafe(Vector<String> list, String target) {
        if(target == null || "".equals(target)) {
            return list;
        }

        //该方法是单现成的,所以安全
        for(int i = 0; i < list.size(); i++) {
            if(target.equals(list.get(i))) {
                list.remove(target);
            }
        }
        return list;
    }


    public static void main(String[] args) {
        //定义了一个动态数组,并向动态数组中添加3个元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        //从这个动态数组中移除3这个元素
        demo.removeOneBySafe(vector, "3");

        System.out.println("移除后的数组内容:" + vector);
    }
}

如上所述,上述代码并没有使用增强的for循环来遍历数组,也不用Iterator迭代器来遍历数组。那么前两者与最后一个例子的区别在哪里呢?这是因为无论是增强的for循环还是迭代器在运行的时候迭代器多了一个线程来控制游标位置,控制游标当前的指向位置,而案例3是单线程的,只有一个i下标指向当前元素位置,读和写操作都在同一线程中完成的,故而安全。

同步类容器的使用

同步类容器如Vector,Hashtable等这些容器的同步功能都是有JDK的Collections.synchronized***等工厂方法去创建实现的。其底层机制用synchronized关键字对每个公用的方法都进行同步,或者使用Object mutex对象锁的机制使得每次只能有一个线程访问容器的状态。使用的代码如下:

   public static void main(String[] args) {
        //定义了一个动态数组,并向动态数组中添加3个元素
        Vector<String> vector = new Vector<String>();
        vector.add("1");
        vector.add("2");
        vector.add("3");

        UseVector demo = new UseVector();

        Collection<String> col = Collections.synchronizedCollection(vector); //先将线程不安全的同步类vectory转为线程安全的col
        System.out.println(col);
    }

要想把同步类变成线程安全,先使用Collections.synchronized***方法将vectory转为线程安全的Colection接口的子类,然后在来操作该方法返回的引用,其实现的原理是在底层给对象加锁。

猜你喜欢

转载自blog.csdn.net/zhang5476499/article/details/83066581