为什么需要同步?
当多个线程同时修改共享数据时,可能会产生竞态条件(Race Condition)。
public class Counter { private int count = 0; public void increment() { count++; // 非原子操作 } }
count++
实际包含读取、加1、写入三个步骤,可能导致多个线程互相覆盖结果,此时必须通过同步机制保证操作的原子性。
synchronized关键字
这是Java最基础的同步机制,通过内置锁(Monitor)实现。
同步方法
public synchronized void increment() { count++; }
整个方法成为临界区,同一时刻只允许一个线程访问
同步代码块
public void increment() { synchronized(this) { count++; } }
可以指定任意对象作为锁,建议使用专用锁对象:
private final Object lock = new Object(); synchronized(lock) { // 临界区代码 }
Lock接口
java.util.concurrent.locks
包提供更灵活的锁机制。
ReentrantLock示例
private Lock lock = new ReentrantLock(); public void increment() { lock.lock(); try { count++; } finally { lock.unlock(); // 必须保证释放 } }
优势:
- 可尝试获取锁(
tryLock()
) - 支持公平锁
- 可中断的获取锁
volatile关键字
保证变量的可见性(不保证原子性),适用于状态标志:
private volatile boolean isRunning = true; public void stop() { isRunning = false; }
适用场景:
- 单写多读场景
- 状态标记变量
原子类(Atomic Classes)
java.util.concurrent.atomic
包提供线程安全的原子操作类:
AtomicInteger atomicCount = new AtomicInteger(0); atomicCount.incrementAndGet(); // 原子自增
实现原理:
- 基于CAS(Compare-And-Swap)机制
- 无锁算法,性能优于同步锁
并发集合
使用线程安全的集合类替代传统集合:
| 不安全类 | 安全替代方案 |
|—————|————————-|
| ArrayList | CopyOnWriteArrayList |
| HashMap | ConcurrentHashMap |
| HashSet | ConcurrentHashSet |
示例:
ConcurrentMap<String, Integer> map = new ConcurrentHashMap<>(); map.put("key", 1); // 线程安全操作
ThreadLocal
解决线程间变量共享问题:
private ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
每个线程拥有独立副本,避免同步开销。
最佳实践原则
- 锁粒度:尽量缩小同步范围
- 避免死锁:按固定顺序获取锁
- 性能考虑:优先使用原子类和无锁结构
- 资源释放:Lock必须在finally块中释放
- 并发测试:使用JUC测试工具验证正确性
通过正确选择同步机制,开发者可以在保证线程安全的前提下,实现高效并发处理,建议根据具体场景选择:
- 简单同步 →
synchronized
- 复杂控制 →
ReentrantLock
- 计数器场景 → 原子类
- 集合操作 → 并发容器
引用说明
本文技术要点参考自:
- Oracle官方Java文档
- 《Java并发编程实战》(Brian Goetz著)
- Java Language Specification第17章
- JUC包源码实现分析
原创文章,发布者:酷盾叔,转转请注明出处:https://www.kd.cn/ask/5203.html