ArrayList、LinkedList、Queue、HashMap、TreeMap、HashSet、TreeSet …都是线程不安全的。
如果多个线程修改同一个 ArrayList(或是其他的),都会出现线程不安全问题。
如果是一个线程修改,则没事。
如果是多个线程读取,也没事。
HashMap 是线程不安全的。
HashTable 和 ConcurrentHashMap 是线程安全的。
HashTable 和 ConcurrentHashMap的区别
多线程环境下使用 哈希表时:
哈希表的结构是 数组 + 链表 / 红黑树。
- HashTable:
HashTable (不推荐)单纯使用一个 sychronized 进行加锁,
具体相当于针对整个 HashTable 对象,坏处就是锁冲突的概率非常高。
- ConcurrentHashMap:
ConcurrentHashMap 内部针对多线程做出了一定的优化。(推荐使用)
1. 并不是针对整个对象加一把锁,而是分成了很多把锁。
每个链表 / 红黑树都分配一把锁。
只有当两个线程恰好修改同一个链表 / 红黑树的时候,才会涉及到锁冲突。
2. 针对读操作,可以直接不加锁。
如果读不加锁,读操作可能会读到一个写之前的值,也可能读到一个写之后的值,还可能读到一个写中间的值。
大部分场景对于读的线程安全操作没有那么高的要求。
3. 内部广泛的使用了CAS 操作,来提高效率。
比如:获取元素个数的时候,没有加锁,直接 CAS 。
修改元素、获取对应的链表的下标的时候,也用 CAS。
4. 针对扩容进行了优化
Hash表的扩容比较麻烦,需要把整张表都拷贝一份。
如果是 HashTable,某个线程 t 正好触发了扩容,那这个线程 t 就要负责完成整个扩容过程。
如果是ConcurrentHashMap,把扩容任务分散开了 。形如“蚂蚁搬家”,一次只扩容一点,由n个线程,每次操作一点每次操作一点。防止只针对一个线程,就可以平滑的进行过渡。