非线程安全的数据结构:ArrayList,LinkedList,ArrayQueue,HashMap,HashSet
线程安全的数据结构:Vector,Stack,Hashtable,CopyOnWriteArrayList,ConcurrentHashMap
看源码
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
// 该方法是容量保障,当容纳不下新增的元素时会进行扩容
elementData[size++] = e;
return true;
}
分析:
elementData[size] = e1; elementData[size] = e2; size++; size++; 的情况Vector的add()源码:
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
分析:
Vector的add方法加了synchronized ,而ArrayList没有,所以ArrayList线程不安全,但是,由于Vector加了synchronized ,变成了串行,所以效率低。
CopyOnWrite容器即写时复制的容器。
// java.util.concurrent包下
List<String> list = new CopyOnWriteArrayList<String>();
通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。
CopyOnWriteArrayList(写数组的拷贝)是ArrayList的一个线程安全的变体,CopyOnWriteArrayList和CopyOnWriteSet都是线程安全的集合,其中所有可变操作(add、set等等)都是通过对底层数组进行一次新的复制来实现的。
它绝对不会抛出ConcurrentModificationException的异常。因为该列表(CopyOnWriteArrayList)在遍历时将不会被做任何的修改。
CopyOnWriteArrayList适合用在“读多,写少”的并发场景中,比如缓存、白名单、黑名单。它不存在“扩容”的概念,每次写操作(add or remove)都要copy一个副本,在副本的基础上修改后改变array引用,所以称为“CopyOnWrite”,因此在写操作要加锁,并且对整个list的copy操作时相当耗时的,过多的写操作不推荐使用该存储结构。
读的时候不需要加锁,如果读的时候有多个线程正在向CopyOnWriteArrayList添加数据,读还是会读到旧的数据,因为开始读的那一刻已经确定了读的对象是旧对象。
不清楚的小白看看之前两篇文章,就可以很容易搞懂HashMap的底层实现原理了。
单看 HashMap 中的 put 操作:
// java.util.concurrent包下
Map<Integer, String> map = new ConcurrentHashMap<>();
Lock 锁。
synchronized锁。扩容的过程:
HashTable和HashMap的实现原理几乎一样,差别在于:
但是HashTable线程安全的策略实现代价却比较大,get/put所有相关操作都是synchronized的,这相当于给整个哈希表加了一把全表锁。当一个线程访问或操作该对象,那其他线程只能阻塞。
所以说,Hashtable 的效率低的离谱,几近废弃。
总结:
提示:这里对文章进行总结:
以上就是今天的学习内容,本文是Java数据结构和多线程的应用学习,认识常用数据结构的线程安全性,理解某些集合不安全的原因,以及某些集合底层如何实现的线程安全性。之后的学习内容将持续更新!!!