企业🤖AI智能体构建引擎,智能编排和调试,一键部署,支持私有化部署方案 广告
[TOC] # 简介 **线程不安全的** Collections中定义了synchronizedList(List list)将此ArrayList转化为线程安全的 数组可以保存多个元素,但在某些情况下无法确定到底要保存多少个元素,此时数组将不再适用,因为数组的长度不可变 为了保存这些数目不确定的元素,JDK中提供了一系列特殊的类,这些类可以存储任意类型的元素,并且长度可变,统称为集合,**集合存储引用类型,不存储基本类型** ArrayList集合是程序中最常见的一种集合,它属于引用数据类型(类)。在ArrayList内部封装了一个长度可变的数组,当存入的元素超过数组长度时,ArrayList会在内存中分配一个更大的数组来存储这些元素,因此可以将ArrayList集合看作一个长度可变的数组。 # 使用 如果我们一开始就知道里面放多少个元素就指定多少个元素,不然多次扩容 ~~~ List list = new ArrayList(80); ~~~ 创建集合的常用格式在此说明一下: 导包: ~~~ import java.util.ArrayList; ~~~ 创建对象:与其他普通的引用数据类型创建方式完全相同,但是要指定容器中存储的数据类型: ~~~ ArrayList<要存储元素的数据类型>变量名 = new ArrayList<要存储元素的数据类型>(); ~~~ * 集合中存储的元素,只能为<>括号中指定的数据类型元素; * "<要存储元素的数据类型>"中的数据类型**必须是引用数据类型,不能是基本数据类型** 下面给出8种基本数据类型所对应的引用数据类型表示形式: 基本数据类型 对应的引用数据类型表示形式 基本数据类型 对应的引用 | byte | Byte | | --- | --- | | short | Short | | Int | Integer | | long | Long | | double | Double | | char | Character | | boolean | Boolean | * 存储String类型的元素 ~~~ ArrayList<String> list = new ArrayList<String>(); ~~~ * 存储int类型的数据 ~~~ ArrayList<Integer> list = new ArrayList<Integer>(); ~~~ * 存储Phone类型的数据(要有Phone这个类) ~~~ ArrayList<Phone> list = new ArrayList<Phone>(); ~~~ ## 常用方法 方法声明 功能描述 ~~~ boolean add(Object obj)将指定元素obj追加到集合的末尾 Object get(int index)返回集合中指定位置上的元素 int size() 返回集合中的元素个数 boolean add(int index, Object obj)将指定元素obj插入到集合中指定的位置 Object remve(int index)从集合中删除指定index处的元素,返回该元素 void clear()清空集合中所有元素 Object set(int index,Object obj)用指定元素obj替代集合中指定位置上的元素 ~~~ ## 补充方法 boolean add(int index,Object obj) 功能:在集合中指定index位置,添加新元素obj 功能说明:假设集合list中有元素[“java”,“javaEE”],当使用add(1,“javaWeb”)后,集合list中的元素为[“java”,“javaWeb”,“JavaEE”]。 Object set(int index,Object obj) 功能:用指定元素obj替代集合中指定index位置的元素 功能说明:假设集合list中有元素[“java”,“javaEE”],当使用set(0,“javaWeb”)后,集合list中的元素为[“javaWeb”,“JavaEE”]。 Object remve(int index) 功能:从集合中删除指定index处的元素,返回该元素 功能说明:假设集合list中有元素[“java”,“javaEE”],当使用remove(0)后,集合list中的元素为[“JavaEE”],返回值为“java”。 void clear() 功能:清空集合中所有元素 功能说明:假设集合list中有元素\[“java”,“javaEE”\],当使用clear\(\)后,集合list中的元素为空\[\]。 例如 ~~~ import java.util.ArrayList; public class ArrayListDemo01 { public static void main(String[] args) { // 创建ArrayList集合 ArrayList<String> list = new ArrayList<String>(); // 向集合中添加元素 list.add("stu1"); list.add("stu2"); list.add("stu3"); list.add("stu4"); // 获取集合中元素的个数 System.out.println("集合的长度:" + list.size()); // 取出并打印指定位置的元素 System.out.println("第1个元素是:" + list.get(0)); System.out.println("第2个元素是:" + list.get(1)); System.out.println("第3个元素是:" + list.get(2)); System.out.println("第4个元素是:" + list.get(3)); } } ~~~ # 遍历 ~~~ for (类型 变量: 数组|集合){ //每一次循环自动把数组的内容设置给变量 } ~~~ ~~~ import java.util.ArrayList; public class ArrayListDemo02 { public static void main(String[] args) { //创建ArrayList集合 ArrayList<Integer> list = new ArrayList<Integer>(); //添加元素到集合 list.add(13); list.add(15); list.add(22); list.add(29); //遍历集合 for (int i = 0; i < list.size(); i++) { //通过索引,获取到集合中每个元素 int n = list.get(i); System.out.println(n); } } } ~~~ # Collections.synchronizedList **线程安全的** 底层在集合的所有方法之上加上了synchronized synchronizedList修饰list如下: ~~~ private static List<String> TEST_LIST = Collections.synchronizedList(new ArrayList<String>()); ~~~ 因为ArrayList本身不是线程安全的,通过Collections.synchronizedList可以将其包装成一个线程安全的List。 下面通过一个实例来理解synchronizedList的用法 ~~~ ArrayList<String> res = new ArrayList<>(); res.add("1"); res.add("2"); res.add("3"); List<String> synList = Collections.synchronizedList(res); synchronized (res) { //获取迭代器 Iterator<String> iterator = synList.iterator(); //遍历 while (iterator.hasNext()) { System.out.println(iterator.next()); } } ~~~ **synchronizedList在迭代的时候,需要开发者自己加上线程锁控制代码,因为在整个迭代的过程中如果在循环外面不加同步代码,在一次次迭代之间,其他线程对于这个容器的add或者remove会影响整个迭代的预期效果,所以这里需要用户在整个循环外面加上synchronized(list)** # CopyOnWriteArrayList ArrayList的线程安全的变体,其原理大概可以通俗的理解为:初始化的时候只有一个容器,很常一段时间,这个容器数据、数量等没有发生变化的时候,大家(多个线程),都是读取(假设这段时间里只发生读取的操作)同一个容器中的数据,所以这样大家读到的数据都是唯一、一致、安全的,但是后来有人往里面增加了一个数据,这个时候CopyOnWriteArrayList 底层实现添加的原理是先copy出一个容器(可以简称副本),再往新的容器里添加这个新的数据,最后把新的容器的引用地址赋值给了之前那个旧的的容器地址,**但是在添加这个数据的期间,其他线程如果要去读取数据,仍然是读取到旧的容器里的数据** **如果不是写少读多的场景,使用 CopyOnWriteArrayList 开销比较大,因为每次对其更新操作(add/set/remove)都会做一次数组拷贝。** `CopyOnWriteArrayList`的整个add操作都是在**锁**的保护下进行的。 这样做是为了避免在多线程并发add的时候,**复制出多个副本出来**,把数据搞乱了,导致最终的数组数据不是我们期望的。 `CopyOnWriteArrayList`的`add`操作的源代码如下 ~~~ public boolean add(E e) { //1、先加锁 final ReentrantLock lock = this.lock; lock.lock(); try { Object[] elements = getArray(); int len = elements.length; //2、拷贝数组 Object[] newElements = Arrays.copyOf(elements, len + 1); //3、将元素加入到新数组中 newElements[len] = e; //4、将array引用指向到新数组 setArray(newElements); return true; } finally { //5、解锁 lock.unlock(); } } ~~~ 使用 ~~~ ArrayList<String> res = new ArrayList<String>(); res.add("1"); res.add("2"); res.add("3"); CopyOnWriteArrayList<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>(res); copyOnWriteArrayList.add("4"); copyOnWriteArrayList.add("5"); System.out.println(copyOnWriteArrayList); ~~~