在 Java 中,Collections.synchronizedList、CopyOnWriteArrayList和synchronized都可以用来解决多线程环境下的线程安全问题,但它们的性能和适用场景有所不同。以下是三者的区别和性能对比:
1.Collections.synchronizedList
Collections.synchronizedList是通过对ArrayList进行包装,使用同步锁(synchronized)来保证线程安全。
工作原理:
Collections.synchronizedList在每个方法(如add、get等)上都加了同步锁,确保同一时间只有一个线程可以操作该集合。
优点:
- 线程安全:适用于多线程环境。
- 简单易用:直接通过
Collections.synchronizedList包装即可。
缺点:
- 性能较低:由于每次操作都需要获取锁,多个线程同时访问时会产生锁竞争,性能会下降。
- 迭代器不安全:在使用迭代器遍历时,仍需要手动同步,否则可能抛出
ConcurrentModificationException。
示例代码:
List<String> list = Collections.synchronizedList(new ArrayList<>()); synchronized (list) { // 遍历时需要手动加锁 for (String item : list) { System.out.println(item); } }适用场景:
- 适合读写操作比例相对均衡的场景。
- 适合对线程安全要求较高,但性能要求不高的场景。
2.CopyOnWriteArrayList
CopyOnWriteArrayList是一种线程安全的集合,它的实现方式是写时复制。
工作原理:
- 每次对集合进行写操作(如
add、remove等)时,会复制一份底层数组,修改完成后再将新的数组替换旧的数组。 - 读操作不需要加锁,因为底层数组是只读的,读操作不会影响数据一致性。
优点:
- 读操作性能高:读操作不需要加锁,多个线程可以同时读取数据。
- 线程安全:写操作通过复制数组实现,避免了锁竞争。
- 迭代器安全:迭代器是基于快照的,不会抛出
ConcurrentModificationException。
缺点:
- 写操作性能较低:每次写操作都会复制整个数组,开销较大。
- 内存占用高:频繁的写操作会导致大量的内存分配和垃圾回收。
示例代码:
List<String> list = new CopyOnWriteArrayList<>(); list.add("A"); list.add("B"); for (String item : list) { // 迭代时不需要加锁 System.out.println(item); }适用场景:
- 适合读多写少的场景,例如缓存、配置管理等。
- 不适合频繁写操作的场景。
3.synchronized
synchronized是 Java 提供的关键字,用于显式地对代码块或方法加锁。
工作原理:
synchronized可以用来锁住代码块或方法,确保同一时间只有一个线程可以执行被锁住的代码。
优点:
- 灵活性高:可以精确控制锁的范围。
- 线程安全:通过锁机制保证线程安全。
缺点:
- 性能较低:锁的粒度较大,容易导致线程阻塞和性能下降。
- 代码复杂度高:需要手动管理锁的范围,容易出错。
示例代码:
List<String> list = new ArrayList<>(); synchronized (list) { list.add("A"); list.add("B"); }适用场景:
- 适合需要对代码块或方法进行精细化控制的场景。
- 不适合需要频繁操作集合的场景,容易导致性能瓶颈。
性能对比
| 特性 | Collections.synchronizedList | CopyOnWriteArrayList | synchronized |
|---|---|---|---|
| 线程安全性 | 是 | 是 | 是 |
| 读操作性能 | 中等(需要加锁) | 高(无锁) | 低(需要加锁) |
| 写操作性能 | 中等(需要加锁) | 低(写时复制,开销大) | 低(需要加锁) |
| 迭代器安全性 | 不安全(需手动加锁) | 安全(基于快照) | 不安全(需手动加锁) |
| 适用场景 | 读写均衡场景 | 读多写少场景 | 灵活控制锁的场景 |
| 内存占用 | 低 | 高(写时复制会占用更多内存) | 低 |
总结
Collections.synchronizedList:- 适合读写操作均衡的场景。
- 性能一般,但实现简单。
CopyOnWriteArrayList:- 适合读多写少的场景。
- 读操作性能高,但写操作性能较低,内存占用较高。
synchronized:- 适合需要精确控制锁的场景。
- 性能较低,代码复杂度较高。
选择建议
- 如果你的场景是读多写少,推荐使用
CopyOnWriteArrayList。 - 如果你的场景是读写均衡,推荐使用
Collections.synchronizedList。 - 如果需要对代码块进行精细化的线程安全控制,使用
synchronized。
测试代码:
public class ListTest { private static void synchronizedTest() throws InterruptedException { long start = System.currentTimeMillis(); /** * ArrayList 是非线程安全的,多个线程同时操作时可能导致索引越界。 * * Exception in thread "Thread-20" java.lang.ArrayIndexOutOfBoundsException: 10 * at java.util.ArrayList.add(ArrayList.java:465) */ List<String> list = new ArrayList<>(); int threadCount = 1000; CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } // 626, 617 synchronized (list) { list.add("1"); } countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("list.size():" + list.size() + " 耗时:" + (System.currentTimeMillis() - start)); for (String content : list) { System.out.println("content:" + content); } } private static void synchronizedListTest() throws InterruptedException { long start = System.currentTimeMillis(); /** * ArrayList 是非线程安全的,多个线程同时操作时可能导致索引越界。 * * Exception in thread "Thread-20" java.lang.ArrayIndexOutOfBoundsException: 10 * at java.util.ArrayList.add(ArrayList.java:465) */ List<String> list = Collections.synchronizedList(new ArrayList<>());// 622 int threadCount = 1000; CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } list.add("1"); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("list.size():" + list.size() + " 耗时:" + (System.currentTimeMillis() - start)); for (String content : list) { System.out.println("content:" + content); } } private static void copyOnWriteArrayListTest() throws InterruptedException { long start = System.currentTimeMillis(); /** * ArrayList 是非线程安全的,多个线程同时操作时可能导致索引越界。 * * Exception in thread "Thread-20" java.lang.ArrayIndexOutOfBoundsException: 10 * at java.util.ArrayList.add(ArrayList.java:465) */ List<String> list = new CopyOnWriteArrayList<>();// 622 int threadCount = 1000; CountDownLatch countDownLatch = new CountDownLatch(threadCount); for (int i = 0; i < threadCount; i++) { new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e) { throw new RuntimeException(e); } list.add("1"); countDownLatch.countDown(); }).start(); } countDownLatch.await(); System.out.println("list.size():" + list.size() + " 耗时:" + (System.currentTimeMillis() - start)); for (String content : list) { System.out.println("content:" + content); } } public static void main(String[] args) throws InterruptedException { synchronizedTest(); synchronizedListTest(); copyOnWriteArrayListTest(); }