
本文深入解析 synchronized 在多线程环境下的作用边界,指出仅同步写操作而忽略读操作(如 size()、stream())会导致竞态条件,并通过代码示例阐明如何真正保证 ArrayList 的线程安全性。
本文深入解析 `synchronized` 在多线程环境下的作用边界,指出仅同步写操作而忽略读操作(如 `size()`、`stream()`)会导致竞态条件,并通过代码示例阐明如何真正保证 arraylist 的线程安全性。
在 Java 多线程编程中,synchronized 是最基础且关键的同步机制,但其作用范围常被误解——它不自动保护整个对象的全部行为,而是仅对加锁代码块(或方法)内的执行提供互斥访问。你提供的代码看似为 list.add() 加了锁,却仍出现 list.size() 输出小于 101 的现象,根本原因在于:同步缺失发生在“读”端,而非“写”端。
? 问题根源剖析
你的代码存在两个典型并发陷阱:
未等待子线程完成(丢失执行结果)
new Thread(...).start()启动 101 个线程后,主线程立即执行list.size()—— 此时大量子线程可能尚未完成add()操作。即使加了synchronized (list),若主线程不等待,就相当于在“中途快照”,必然导致size()偏小。
✅ 正确做法:使用Thread.join()确保所有子线程执行完毕后再读取。-
读操作未同步(引发可见性与竞态问题)
ArrayList是非线程安全集合,Javadoc 明确指出:“若多个线程并发访问且至少一个修改结构(如add),则必须外部同步”。注意:这里的“访问”包含所有共享状态读写操作,包括size()、isEmpty()、stream()等。
❌ 当前代码中,list.size()和list.stream()在主线程中直接调用,未加锁,可能读到:- 过期的缓存值(可见性问题);
- 正被其他线程修改中的中间状态(如
size字段已更新但元素数组未刷新); -
stream()内部遍历时遇到并发修改(虽无ConcurrentModificationException,但结果不可靠)。
✅ 正确实现:全路径同步 + 线程协调
import java.util.ArrayList;
import java.util.List;
public class SafeListExample {
public static void main(String[] args) throws InterruptedException {
List<string> list = new ArrayList();
List<thread> threads = new ArrayList();
// 启动并保存所有子线程
for (int i = 0; i {
synchronized (list) {
list.add(Integer.toString(finalI));
}
});
t.start();
threads.add(t);
}
// 主线程等待所有子线程完成(关键!)
for (Thread t : threads) {
t.join();
}
// 所有读操作也必须同步,确保可见性与一致性
synchronized (list) {
System.out.println("Size: " + list.size()); // → 101
long sum = list.stream()
.mapToInt(Integer::parseInt)
.sum();
System.out.println("Sum: " + sum); // → 5050
}
}
}</thread></string>
? 提示:
list.stream().count()返回正确值是偶然的——因count()是终端操作,底层可能触发完整遍历+计数,在特定 JVM 实现和调度下“碰巧”看到最终状态,但绝不可依赖。线程安全必须通过显式同步或线程安全容器保障。
Alibabacloud Sdk Client Initialization For Java下载在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
? 关键总结与最佳实践
-
synchronized(obj)锁定的是临界区代码,不是对象本身;任何线程想安全访问该对象的共享状态,都必须进入同一把锁保护的代码块。 - 主线程也是线程:它对共享对象的读写同样需同步,不可豁免。
- 更优方案:优先选用线程安全集合(如
CopyOnWriteArrayList适合读多写少场景),或使用Collections.synchronizedList(new ArrayList())(注意:它仅包装单个方法,复合操作仍需手动同步)。 - 调试建议:启用
-XX:+UnlockDiagnosticVMOptions -XX:+PrintConcurrentLocks查看锁竞争,或使用 JMH 进行并发基准测试。
理解 synchronized 的本质,就是理解“同步的不是数据,而是对数据的访问时序”。只有当所有读写路径都被同一把锁覆盖,才能真正构建可预测的并发行为。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











