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

同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作,比如如下:
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将变为9
C: 执行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());
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值