个人主页:杨永杰825_Spring,Mysql,多线程-CSDN博客

相关链接:ArrayList介绍-CSDN博客 ⭐每日一句:成为架构师路途遥远

欢迎大家:关注+点赞+评论+收藏⭐️

目录

前言

不安全案例

CopyOnWriteArrayList

特点

常用方法

案例

底层原理

前言

ArrayList是线程不安全的数据结构,这意味着当多个线程同时访问或修改ArrayList时,可能会导致数据一致性的问题。当多个线程同时对ArrayList进行写操作(例如添加、删除、修改元素),可能会导致其中一个线程的操作被覆盖或丢失。这是因为ArrayList在进行修改操作时并没有进行同步处理,因此多个线程之间的操作顺序是不确定的。

不安全案例

下面是一个示例,展示了当多个线程同时访问一个ArrayList时可能出现的线程不安全问题:

import java.util.ArrayList;

import java.util.List;

public class ArrayListThreadUnsafeExample {

public static void main(String[] args) {

// 创建一个空的ArrayList

List arrayList = new ArrayList<>();

// 创建两个线程,同时向ArrayList中添加元素

Thread thread1 = new Thread(new AddElementTask(arrayList));

Thread thread2 = new Thread(new AddElementTask(arrayList));

thread1.start();

thread2.start();

try {

thread1.join();

thread2.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("ArrayList size: " + arrayList.size());

}

// 添加元素的任务

static class AddElementTask implements Runnable {

private List arrayList;

public AddElementTask(List arrayList) {

this.arrayList = arrayList;

}

@Override

public void run() {

for (int i = 0; i < 1000; i++) {

// 向ArrayList中添加元素

arrayList.add(i);

}

}

}

}

结果:

在上述示例中,我们创建了一个空的ArrayList,并启动两个线程分别向该ArrayList中添加1000个元素。由于ArrayList是线程不安全的,因此可能出现以下两种情况之一:

在添加元素的过程中,其中一个线程的操作被覆盖或丢失,导致最终ArrayList的大小小于2000。在添加元素的过程中,两个线程同时修改ArrayList,导致其中一个线程的操作被覆盖或丢失,最终ArrayList的大小可能大于2000。

因此,使用多个线程同时对ArrayList进行操作时,不能保证最终结果的正确性和一致性。为了解决这个问题,可以使用线程安全的替代类(如Vector或CopyOnWriteArrayList)或者使用同步机制来保证线程安全。

CopyOnWriteArrayList

CopyOnWriteArrayList是Java集合框架中的一个线程安全的List实现类。它通过对底层数组进行复制来实现线程安全,因此在迭代操作时,不需要进行额外的同步措施。

特点

线程安全:CopyOnWriteArrayList通过加锁的方式实现线程安全。在多线程环境下,可以同时进行读操作,只有在写操作时才需要进行加锁。读写分离:CopyOnWriteArrayList通过对底层数组进行复制,在写操作时复制一份新的数组,读操作则直接在原数组上进行。这样做的好处是,读操作不需要加锁,不会阻塞其他读操作,提高了并发性能。但是写操作需要加锁,并且写操作期间对原数组的读操作会被阻塞。弱一致性:由于CopyOnWriteArrayList在写操作时是复制一份新的数组,因此在写操作期间,读操作可能会读取到旧的数据。所以CopyOnWriteArrayList提供的是弱一致性的读写一致性。适合读多写少的场景:CopyOnWriteArrayList适用于读多写少的场景,例如:日志系统、缓存等。

常用方法

add(E e):向列表末尾添加元素。remove(int index):移除指定索引处的元素。get(int index):获取指定索引处的元素。size():获取列表的大小。

总结:CopyOnWriteArrayList是一个线程安全的List实现类,通过读写分离和对底层数组进行复制来实现线程安全。它适用于读多写少的场景,并且提供了弱一致性的读写一致性。

案例

以下是一个示例代码,展示了如何使用CopyOnWriteArrayList类:

import java.util.Iterator;

import java.util.concurrent.CopyOnWriteArrayList;

public class CopyOnWriteArrayListExample {

public static void main(String[] args) {

// 创建一个CopyOnWriteArrayList

CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

// 向列表中添加元素

list.add("A");

list.add("B");

list.add("C");

// 创建一个线程,在迭代过程中添加元素

Thread thread = new Thread(() -> {

Iterator iterator = list.iterator();

while (iterator.hasNext()) {

String element = iterator.next();

System.out.println(element); // 输出当前迭代的元素

if (element.equals("B")) {

list.add("D"); // 在迭代过程中添加元素

}

}

});

thread.start();

try {

thread.join();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(list);

}

}

在上述示例中,我们创建了一个CopyOnWriteArrayList并向其添加了三个元素。然后我们创建了一个线程,在迭代CopyOnWriteArrayList的过程中添加了一个新元素。CopyOnWriteArrayList是线程安全的,因此在迭代过程中添加元素不会导致ConcurrentModificationException异常。

输出结果可能是:

A

B

C

[A, B, C, D]

可以看到,即使在迭代过程中添加了元素,最终的CopyOnWriteArrayList仍然包含了新添加的元素。这是因为CopyOnWriteArrayList在添加和修改操作时,会创建一个新的拷贝并进行操作,从而保证了原始数据的线程安全性。

底层原理

CopyOnWriteArrayList的底层原理是通过使用一个可变的数组来存储数据。在对数组进行修改(添加/删除)操作时,并不会直接修改原始数组,而是创建一个新的副本,并在副本上进行修改。这意味着对数组进行修改不会影响到正在进行迭代的线程,因此可以实现并发安全性。

具体实现步骤如下:

初始化时,CopyOnWriteArrayList会创建一个空数组,用于存储元素。 当添加元素时,CopyOnWriteArrayList会创建一个当前数组长度 + 1 大小的新数组,并将原始数组中的所有元素复制到新数组中,然后再将新元素添加到新数组的最后。 当删除元素时,CopyOnWriteArrayList会创建一个当前数组长度 - 1 大小的新数组,并将原始数组中除了要删除的元素之外的其他元素复制到新数组中。

通过这种方式,在对数组进行修改时,不会改变原始数组,而是创建一个新的副本,从而实现了并发安全性。同时,通过使用读写分离的思想,读操作可以并发进行,不需要加锁,提高了读操作的性能。

需要注意的是,由于每次修改操作都会创建一个新的数组副本,因此CopyOnWriteArrayList的内存消耗较高,适用于读操作较多而写操作较少的场景。

 

推荐文章

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: