您的位置:首页 > 编程语言 > Java开发

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

2017-05-18 11:45 218 查看
同步容器类都是线程安全的,但在某些情况下可能需要额外的客户端加锁来保护复合操作,比如如下:

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&
c213
lt;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());
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: