为什么ArrayList 是非线程安全的集合类?
arduino
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("1");
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
运行结果会报错,或者打印出来的不对、
原因
ArrayList 是非线程安全的集合类
多个线程同时对 list 进行修改操作(add)和读取操作(toString 内部会遍历)
当一个线程正在遍历时,另一个线程修改了集合结构,就会抛出 ConcurrentModificationException
解决方案
1.使用synchronized
关键字
typescript
public class Demo {
public static void main(String[] args) {
ArrayList<String> list = new ArrayList<>();
Object lock = new Object();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (lock) {
list.add("1");
System.out.println(list);
}
}, String.valueOf(i)).start();
}
}
}
2.使用vector
类
Vector 是 Java 中的一个线程安全的集合类
与 ArrayList 相比,Vector 的方法都是同步的(synchronized)
适用于多线程环境下的使用场景

查看源码可以看到里边就是使用的synchronized
关键字。
3.使用Collections.synchronizedList()
工具类的方法
typescript
public class Demo {
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
Object lock = new Object();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
synchronized (lock) {
list.add("1");
System.out.println(list);
}
}, String.valueOf(i)).start();
}
}
4.使用 CopyOnWriteArrayList(推荐)
arduino
import java.util.concurrent.CopyOnWriteArrayList;
public class Demo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (int i = 0; i < 10; i++) {
new Thread(() -> {
list.add("1");
System.out.println(list);
}, String.valueOf(i)).start();
}
}
}
CopyOnWriteArrayList
CopyOnWriteArrayList
这个是专门设计解决并发环境的线程安全集合。
我们去查看源码:
写操作

你看到这里边也使用了synchronized
关键字,使用了lock
;这是因为写的时候需要保证数据的一致性。
但是读操作就不需要加锁

CopyOnWriteArrayList
是如何工作的 ?
- 读操作 (如
get()
、size()
)是非常高效的,因为它不涉及锁。每次读操作直接访问底层的数组(它的副本),从而保证了线程安全。 - 写操作 (如
add()
、remove()
、set()
)需要复制整个数组,修改副本,然后用新的数组替换旧的数组。这种策略避免了读操作被修改中的数组影响,但同时,写操作在多线程中必须进行同步,以防止在修改数组时出现不一致。
为什么同时使用 synchronized
和 lock
?
在CopyOnWriteArrayList
中使用了synchronized
与lock
;
synchronized
与 lock
的区别:
synchronized
是 Java 内置的同步机制 ,直接在方法或代码块上加锁,锁的粒度较大,通常适用于简单的同步需求。lock
是通过显式的锁 (如ReentrantLock
)来控制访问,它提供了比synchronized
更强大的功能,如尝试加锁(tryLock()
),中断锁等待等。lock
允许更精细的控制,可以在获取锁时设置超时、获取公平性等。
在 CopyOnWriteArrayList
中的应用:
CopyOnWriteArrayList
是线程安全的 集合类,它的底层实现通常会在添加、删除、更新元素时创建一个新的数组,并通过锁来确保写操作的线程安全。lock
和synchronized
可能用于不同场景中的加锁:- 使用
lock
:
-
- 在
CopyOnWriteArrayList
的内部,lock
可能用于更精细的控制,特别是在 写操作 时对底层数组进行复制和修改的过程中。lock
提供了 可中断的锁 、尝试获取锁 、公平性控制 等高级功能,这些都可能被用在性能要求较高的场景中。
- 在
- 使用
synchronized
:
-
synchronized
可能用于 其他部分的同步 ,例如在某些写操作(如add()
)的开始和结束之间。synchronized
更适合对临界区(即操作共享资源的代码块)进行加锁。
ini
public boolean add(E e) {
synchronized (lock) { // 使用 lock 来同步
Object[] es = getArray();
int len = es.length;
es = Arrays.copyOf(es, len + 1);
es[len] = e;
setArray(es);
return true;
}
}
在这段源码中,synchronized (lock)
用于 同步访问,保证多个线程在执行添加元素的操作时不会发生数据冲突。
总结
优点 :线程安全、高效读操作、不需要显式加锁、适用于读多写少 的场景。
缺点 :写操作性能差、内存开销大、不适合频繁写操作 的场景。
适用场景:适用于读多写少、写操作较少、并发读操作频繁的场景。
CopyOnWriteArrayList
在某些高并发环境中是非常有用的,尤其是希望避免锁竞争、提高读取性能 时。然而,它并不适合所有的场景,特别是写操作较多的情况下,可能需要考虑其他更适合的并发数据结构,如 ConcurrentLinkedQueue
或 ReentrantLock
等。