java 里面线程安全的容器。
1,概述
java里面的容器有,List, Set 和 Map, 其实现类有ArrayList,LinkedList ,hashSet,TreeSet,hashMap 和TreeMap等等。然而需要使用线程安全的容器有,Vector , hashTable,当然了,java里面提供了collections工具类,该类里面提供了一系列的构造线程安全的容器的方法:synchronizedCollection
,synchronizedList
,synchronizedMap
,synchronizedSet
等等。这些容器实现线程同步的原理都是加synchronized关键字。但是,synchronized是以牺牲性能作为代价来实现线程安全的。有没有一种解决方案,既能实现线程同步,又可以在效率上比synchronized更高的呢?于是java的设计者们在JDK1.5以后,引入了java.util.concurrent包,该包里面提供的工具,可以大大提高线程同步的效率。
2,java.util.concurrent与java.util里面的容器源码之对比。
Vector与CopyOnWriteArrayList,
Vector用来存储的变量:
protected Object[] elementData;
CopyOnWriteArrayList里面的用来存储的变量:
private volatile transient Object[] array;
volatile关键字,可以确保当有多个线程更改同一变量的时候,保证他们所更改的是同一个变量,(因为线程的工作原理是先将对象拷贝一份到自己的线程内存里面,当操作完成之后再将值写回堆空间)
Vector里面的add方法:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
CopyOnWriteArrayList里面的add方法:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
可以看出CopyOnWriteArrayList里面添加新元素的办法是复制一个原有的数组,长度比原来的数组多了一位,然后在将新的元素放在多出来的那个位置,然后在将原来的数组替换掉。
Vector里面的删除方法:
public synchronized E remove(int index) {
modCount++;
if (index >= elementCount)
throw new ArrayIndexOutOfBoundsException(index);
E oldValue = elementData(index);
int numMoved = elementCount - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--elementCount] = null; // Let gc do its work
return oldValue;
}
CopyOnWriteArrayList里面的删除方法:
public boolean remove(Object o) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
if (len != 0) {
// Copy while searching for element to remove
// This wins in the normal case of element being present
int newlen = len - 1;
Object[] newElements = new Object[newlen];
for (int i = 0; i < newlen; ++i) {
if (eq(o, elements[i])) {
// found one; copy remaining and exit
for (int k = i + 1; k < len; ++k)
newElements[k-1] = elements[k];
setArray(newElements);
return true;
} else
newElements[i] = elements[i];
}
// special handling for last cell
if (eq(o, elements[newlen])) {
setArray(newElements);
return true;
}
}
return false;
} finally {
lock.unlock();
}
}
看到这里笔者也是有点晕,拿add方法来说,vector添加一个元素,就是在同一个数组上添加的,而CopyOnWriteList里面,添加一个元素确要重新将这个数组拷贝一份,这会需要很多的开销,但是为什么要这样做呢?
1,如果我们创建一个ArrayList的话,一般情况是需要查询遍历的操作要大于修改插入的操作,(如果我们想要一个插入或删除操作很多的list的话我们可以创建LinkedList)
在这种情况下,CopyOnWrite的方式的缺点显的没那么大。
2,“快照”风格的迭代器方法在创建迭代器时使用了对数组状态的引用。此数组在迭代器的生存期内不会更改,因此不可能发生冲突,并且迭代器保证不会抛出 ConcurrentModificationException。创建迭代器以后,迭代器就不会反映列表的添加、移除或者更改。在迭代器上进行的元素更改操作(remove、set 和 add)不受支持。这些方法将抛出 UnsupportedOperationException。
这就是CopyOnWriteList,同样CopyOnWriteSet底层是用CopyOnWriteList来实现的。