java多线程(七) 之 同步容器类

来源:互联网 发布:java 线程休眠 编辑:程序博客网 时间:2024/05/21 21:35

同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作,比如如下:
1.线程不安全的复合操作1:

import java.util.Vector;public class UnsafeVectorOperation {    public static Object getLast(Vector<?> list){        int lastIndex = list.size() - 1;        return list.get(lastIndex);    }    public static void deleteLast(Vector<?> list){        int lastIndex = list.size() -1;        list.remove(lastIndex);    }}

线程B可能获取了list.size()-1的值之后,此时如果线程A执行了list.remove(lastIndex),那么线程B再接着执行return list.get(lastIndex);将会报ArrayIndexOutOfBoundsException异常

2.线程不安全的复合操作2:
如果给方法加上synchronized锁呢?

import java.util.Vector;public class UnsafeVectorOperation2 {    public synchronized static Object getLast(Vector<?> list){        int lastIndex = list.size() - 1;        return list.get(lastIndex);    }    public synchronized static void deleteLast(Vector<?> list){        int lastIndex = list.size() -1;        list.remove(lastIndex);    }}

结论是,线程仍然不安全,理由:
虽然按照上一题的中的线程A,B之间是安全的,但是,如果是下面的情况:
Vector list;
A线程:UnsafeVectorOperation2.getLast(list)
C线程:list.remove(list.size()-1);
所以上面的代码只能限制线程同时调用UnsafeVectorOperation2里的方法安全,一旦有其他线程也调用list,那么上面的代码就傻逼了.

3.如果给list上锁,使得复合操纵变为原子操作.

import java.util.Vector;public class UnsafeVectorOperation3 {    public static Object getLast(Vector<?> list){        synchronized (list) {            int lastIndex = list.size() - 1;            return list.get(lastIndex);        }    }    public static void deleteLast(Vector<?> list){        synchronized(list){            int lastIndex = list.size() -1;            list.remove(lastIndex);        }    }}

UnsafeVectorOperation3 当中的确不存在线程不安全的问题了,但是其他线程对list的操作仍然线程不安全,这个不安全可以来自其他线程对UnsafeVectorOperation3的调用,例如:

Vector  list;线程B调用:deleteLast(list)线程C执行: for(int i =0;i<list.size();i++)  list.get(i);/****************************/假设size为10,C的i已经遍历到了9如果B,C线程执行的情况是:C: 执行i<list.size() //i=9<size=10,继续遍历B: 执行了deleteLast(list)  //size将变为9C: 执行list.get(i) //报ArrayIndexOutOfBoundsException异常

4.带有客户端锁的迭代,这下安全了,只不过…

synchronized(list){    for(int i =0;i<list.size();i++)      list.get(i);}

在遍历期间,将会导致其他线程对list的访问,数据量越大,迭代越长,并发效率大大降低.很多时候得不偿失.

5.使用迭代器来迭代,解决并发效率低的问题:
至今所有的容器都并没有真正达到”既百分百安全,效率又很高”,而只能采取折中的方案.

for(Object o:list){}

迭代器是一种权衡利弊的考虑,采取的是”及时失败”的策略,一旦迭代的数据被修改掉,就会抛出一个ConcurrentModificationException异常
开发的时候,可能需要根据不同的实际情况,选择合适的方案,3,4,5的程序例子都是可以使用的.

6.隐藏迭代器:
有时候,你可能会犯下面的错误:
目的:设计一个百分之百安全的类,代码如下:

import java.util.HashSet;import java.util.Random;import java.util.Set;public class HiddenIterator {    private final Set<Integer> set = new HashSet<Integer>();    public synchronized void add(Integer i){        set.add(i);    }    public synchronized void remove(Integer i){        set.remove(i);    }    public void addTenRandomNum(){        Random r = new Random();        for(int i = 0;i<10;i++){            add(r.nextInt());        }        //容易犯错误的地方,set.toString()在set集合里会进行迭代操作,而并没有上锁        //在集合的操作里面,有些容易让人忘记的迭代操作: toString,containsAll,removeAll,retainAll        //这些间接迭代的操作都有可能抛出ConcurrentModificationException异常        System.out.println("DEBUG: added ten elements to "+set.toString());    }}
原创粉丝点击