一、Java的集合类有哪些?
二、如何定义集合是线程不安全的?
当多个并发同时对线程不安全的集合进行增删改的时候会破坏这些集合的数据完整性,例如:当多个线程访问同一个集合或Map时,如果有超过一个线程修改了ArrayList集合,则程序必须手动保证该集合的同步性。这就说明集合是线程不安全的。
三、实例:验证ArrayList线程不安全,抛出异常 ConcurrentModificationException
import java.util.*;
/**
* @author 蓝多多的小仓库
* @title: mytest
* @projectName test
* @description: ldd_annotation
* @date 2022/11/10 16:39
*/
public class mytest {
public static void main (String[] args) {
List<Integer> list = new ArrayList<Integer>();
for (int i = 0; i < 10; i++) {
final int j = i;
new Thread(() -> {
list.add(j);
System.out.println(list);
}, "" + i).start();
}
}
}
运行结果(结果不是唯一的,也有可能不抛出异常,你可以多运行几次):
四、Java线程安全的集合类有哪些?
线程安全类是确保类的内部状态以及从方法返回的值在从多个线程并发调用时是正确的类。Java线程安全的集合有Vector、HashTable、Stack、ArrayBlockingQueue、ConcurrentHashMap、ConcurrentLinkedQueue等。
Vector:相当于 ArrayList 的翻版,是长度可变的数组,只要是关键性的操作,方法前面都加了synchronized关键字修饰,来保证线程的安全性。Vector类可以支持add(),remove(),get(),size()等方法,实例如下:
import java.util.*;
public class mytest {
public static void main (String[] args) {
Vector vector = new Vector();
vector.add(2);
vector.add(4);
vector.add("LDD");
vector.add(1);
vector.add("LYD");
System.out.println("向量: " + vector);
vector.remove(1);
System.out.println("移除元素后向量为: " + vector);
}
}
Hashtable:是一个线程安全的集合,是单线程集合,它给几乎所有public方法都加上了synchronized关键字。所以相较于Hashmap是线程安全的。
ArrayBlockingQueue:是一个阻塞队列,底层使用数组结构实现,按照先进先出(FIFO)的原则对元素进行排序,是线程安全的。
ConcurrentLinkedQueue:是一种FIFO的无界队列,是线程安全的,它适用于“高并发”的场景。
ConcurrentHashMap:采用了分段锁(Segment),是一种高效但是线程安全的集合。
Stack:栈,也是线程安全的,继承于Vector,栈后进先出。Stack类可以支持许多操作,比如push、pop、peek、search、empty等。实例:
import java.util.*;
public class mytest {
public static void main (String[] args) {
Stack stack = new Stack();
stack.push(2);
stack.push(4);
stack.push(1);
Integer num1 = (Integer) stack.pop();
System.out.println("出栈元素是: " + num1);
Integer num2 = (Integer) stack.peek();
System.out.println("栈顶元素是: " + num2);
}
}
那么使用ArrayList、HashMap,需要线程安全怎么办呢?为了保证集合既是安全的而且效率高,Collections为我们提出了解决方案,把这些集合包装成线程安全的集合。使用Collections装饰的线程安全集合如下:
<T> Collection<T> synchronizedCollection(Collection<T> c); //返回指定collection 对应的线程安全的collection。
static <T> List<T> synchronizedList(List<T> list); //返回指定List对象对应的线程安全的List对象。
static <K, V> Map<K, V> synchronizedMap(Map<K, V> m); //返回指定Map对象对应的线程安全的Map对象。
static <T> Set<T> synchronizedSet(Set<T> s); //返回指定Set对象对应的线程安全的Set对象。
static <K, V> SortedMap<K, V> synchronizedSortedMap(SortedMap<K, V> m); //返回指定SortedMap对象对应的线程安全的SortedMap对象。
java.util.concurrent.*下的线程安全集合类:如:CopyOnWriteArrayList、 CopyOnWriteArraySet等,它们里面包含三类关键词: Blocking、CopyOnWrite、Concurrent Blocking,大部分实现基于锁,并提供用来阻塞的方法。
关于CopyOnWriteArrayList是怎么保证线程安全的?请参考上一篇文章:Java面试笔记:CopyOnWriteArrayList是怎么保证线程安全的?_蓝多多的小仓库的博客-CSDN博客
五、线程不安全的集合类有哪些?
Hashmap:HashMap在put操作的时候,如果插入的元素超过了容量(由负载因子决定)的范围就会触发扩容操作,就是resize,这个会重新将原数组的内容重新hash到新的扩容数组中,在多线程的环境下,存在同时其他的元素也在进行put操作,如果hash值相同,可能出现同时在同一数组下用链表表示,造成闭环,导致在get时会出现死循环,所以HashMap是线程不安全的。
ArrayList: List 对象在做 add 时,执行 Arrays.copyOf 的时候,返回一个新的数组对象。当有线程 A、B… 同时进入 grow方法,多个线程都会执行 Arrays.copyOf 方法,返回多个不同的 elementData 对象,假如,A先返回,B 后返回,那么 List.elementData ==A. elementData,如果同时B也返回,那么 List.elementData ==B. elementData,所以线程B就把线程A的数据给覆盖了,导致线程A的数据被丢失。也可以参考上面提供的实例:验证ArrayList线程不安全,抛出异常 ConcurrentModificationException 来理解ArrayList为什么是不安全的。
LinkedList:与Arraylist线程安全问题相似,线程安全问题是由多个线程同时写或同时读写同一个资源造成的。
HashSet:底层数据存储结构采用了Hashmap,所以Hashmap会产生的线程安全问题HashSet也会产生。
TreeSet、TreeMap等
六、ArrayList为什么会发生并发修改异常?那么如何避免ArrayList的并发问题?
ArrayList是线程不安全的,在多线程并发访问的时候可能会出现问题,会存在线程B把线程A的数据给覆盖了,导致线程A的数据被丢失的情况,也有可能在扩容时有数组索引越界的异常产生。解决方案:
(1)使用Collections.synchronizedList()方法对ArrayList对象进行包装
ArrayList<Integer> arraylist = Collections.synchronizedList(new ArrayList());
(2)使用并发容器CopyOnWriteArrayList
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<String>();
详情请参考:ArrayList为什么会出现并发问题以及相应的解决办法_Max的外企搬砖随笔的博客-CSDN博客