Java 线程同步详解
Java 线程同步详解
线程同步是多线程编程的核心概念,用于协调多个线程对共享资源的访问,防止数据不一致和并发问题。下面我将全面讲解 Java 中的线程同步机制。
一、为什么需要线程同步?
当多个线程访问共享资源时,可能出现:
- 竞态条件:多个线程同时修改同一数据
- 内存可见性问题:一个线程修改数据后,其他线程看不到最新值
- 指令重排序问题:编译器/处理器优化导致代码执行顺序改变
二、Java 同步机制分类
1. 内置锁(synchronized)
// 同步方法
public synchronized void increment() {count++;
}// 同步代码块
public void update() {synchronized(this) {// 临界区代码}
}// 静态方法锁(类级别锁)
public static synchronized void staticMethod() {// ...
}
特点:
- 自动获取/释放锁(进入同步块获取,退出释放)
- 可重入(同一线程可重复获取同一把锁)
- 非公平锁(不保证等待时间最长的线程先获取锁)
2. 显式锁(Lock API)
private final ReentrantLock lock = new ReentrantLock();public void performTask() {lock.lock(); // 手动加锁try {// 临界区代码} finally {lock.unlock(); // 必须手动释放锁}
}
Lock 接口优势:
- 可中断锁(
lockInterruptibly()
) - 超时获取锁(
tryLock(long time, TimeUnit unit)
) - 公平锁选项(
new ReentrantLock(true)
) - 多条件变量(
Condition
)
3. 原子变量(Atomic Classes)
private AtomicInteger count = new AtomicInteger(0);public void safeIncrement() {count.incrementAndGet(); // 原子操作
}
常用原子类:
AtomicInteger
,AtomicLong
AtomicReference
AtomicBoolean
LongAdder
(高并发计数器)
4. volatile 关键字
private volatile boolean running = true;public void stop() {running = false; // 写操作立即对其他线程可见
}
适用场景:
- 状态标志(单个写入者)
- 双重检查锁定模式
- 不保证复合操作的原子性
三、高级同步工具
1. 读写锁(ReadWriteLock)
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = rwLock.writeLock();public String readData() {readLock.lock();try {return data;} finally {readLock.unlock();}
}public void writeData(String value) {writeLock.lock();try {data = value;} finally {writeLock.unlock();}
}
2. 条件变量(Condition)
private final Lock lock = new ReentrantLock();
private final Condition notFull = lock.newCondition();
private final Condition notEmpty = lock.newCondition();public void put(Object item) throws InterruptedException {lock.lock();try {while (queue.isFull()) {notFull.await(); // 等待队列非满}queue.enqueue(item);notEmpty.signal(); // 唤醒等待的消费者} finally {lock.unlock();}
}
3. 同步集合
// 并发Map
Map<String, String> concurrentMap = new ConcurrentHashMap<>();// 阻塞队列
BlockingQueue<Integer> queue = new LinkedBlockingQueue<>(100);// 写时复制列表
List<String> safeList = new CopyOnWriteArrayList<>();
四、线程协调工具
1. CountDownLatch(一次性门闩)
CountDownLatch latch = new CountDownLatch(3);// 工作线程
void worker() {// 执行任务...latch.countDown();
}// 主线程
latch.await(); // 阻塞直到计数归零
System.out.println("所有任务完成");
2. CyclicBarrier(循环屏障)
CyclicBarrier barrier = new CyclicBarrier(4, () -> System.out.println("所有玩家准备就绪"));void player() {prepare();barrier.await(); // 等待其他玩家startGame();
}
3. Semaphore(信号量)
Semaphore semaphore = new Semaphore(5); // 5个许可证void accessResource() {semaphore.acquire(); // 获取许可try {// 使用资源} finally {semaphore.release(); // 释放许可}
}
4. Exchanger(数据交换器)
Exchanger<String> exchanger = new Exchanger<>();// 线程A
String dataA = "Data from A";
String received = exchanger.exchange(dataA);// 线程B
String dataB = "Data from B";
String received = exchanger.exchange(dataB);
五、避免死锁的策略
1. 死锁产生的必要条件
- 互斥条件
- 持有并等待
- 不可抢占
- 循环等待
2. 预防死锁的方法
// 1. 固定锁顺序
public void transfer(Account from, Account to, int amount) {Account first = from.id < to.id ? from : to;Account second = from.id < to.id ? to : from;synchronized(first) {synchronized(second) {// 转账操作}}
}// 2. 尝试获取锁(带超时)
if (lock1.tryLock(1, TimeUnit.SECONDS)) {try {if (lock2.tryLock(1, TimeUnit.SECONDS)) {try {// 操作} finally {lock2.unlock();}}} finally {lock1.unlock();}
}// 3. 使用开放调用(避免在持有锁时调用外部方法)
六、同步性能优化
1. 减少锁竞争
- 缩小同步范围:同步代码块 > 同步方法
- 降低锁粒度:使用多个锁代替单个锁
- 使用读写锁:区分读/写操作
- 无锁数据结构:原子变量、CAS操作
2. 锁消除与锁粗化
// 锁消除(JIT编译器优化)
public String concat(String s1, String s2, String s3) {StringBuffer sb = new StringBuffer();sb.append(s1); // 同步方法但可消除锁sb.append(s2);sb.append(s3);return sb.toString();
}// 锁粗化(减少频繁加锁开销)
synchronized(lock) {operation1();operation2();operation3();
}
3. 并发设计模式
// 1. 生产者-消费者模式
BlockingQueue<Task> queue = new LinkedBlockingQueue<>();// 2. 线程局部存储
private static ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));// 3. Future模式
ExecutorService executor = Executors.newFixedThreadPool(2);
Future<Integer> future = executor.submit(() -> computeExpensiveValue());
// ...其他操作
Integer result = future.get();
七、Java内存模型(JMM)与同步
Happens-Before 规则
- 程序顺序规则
- 监视器锁规则
- volatile变量规则
- 线程启动规则
- 线程终止规则
- 中断规则
- 终结器规则
- 传递性
// 正确同步示例
class SafePublication {private int value;private volatile boolean initialized;public void initialize(int val) {value = val;initialized = true; // volatile写}public int getValue() {if (initialized) { // volatile读return value;}return -1;}
}
八、现代同步实践
1. CompletableFuture(异步编程)
CompletableFuture.supplyAsync(() -> fetchData()).thenApply(data -> process(data)).thenAccept(result -> store(result)).exceptionally(ex -> handleError(ex));
2. StampedLock(乐观读锁)
private final StampedLock lock = new StampedLock();
private double balance;public double readBalance() {long stamp = lock.tryOptimisticRead(); // 乐观读double currentBalance = balance;if (!lock.validate(stamp)) { // 检查是否被修改stamp = lock.readLock(); // 退化为悲观读try {currentBalance = balance;} finally {lock.unlockRead(stamp);}}return currentBalance;
}
3. VarHandle(Java 9+)
class AtomicCounter {private volatile int count;private static final VarHandle COUNT_HANDLE;static {try {COUNT_HANDLE = MethodHandles.lookup().findVarHandle(AtomicCounter.class, "count", int.class);} catch (Exception e) {throw new Error(e);}}public void increment() {int current;do {current = (int) COUNT_HANDLE.getVolatile(this);} while (!COUNT_HANDLE.compareAndSet(this, current, current + 1));}
}
九、同步机制选择指南
场景 | 推荐方案 | 说明 |
---|---|---|
简单同步 | synchronized | 开发简单,自动管理 |
复杂锁控制 | ReentrantLock | 支持超时、中断等 |
读多写少 | ReentrantReadWriteLock | 提高读并发性能 |
计数器 | AtomicInteger/LongAdder | 无锁高性能 |
状态标志 | volatile | 轻量级可见性保证 |
线程协作 | CountDownLatch/CyclicBarrier | 协调多线程执行 |
资源池 | Semaphore | 控制并发访问数量 |
异步编程 | CompletableFuture | 函数式异步处理 |
十、常见同步错误示例
1. 误用 String 锁
// 错误!字符串常量池导致意外共享锁
synchronized("LOCK") {// ...
}
2. 同步方法调用非同步方法
class Account {private int balance;public synchronized void transfer(Account target, int amount) {this.balance -= amount;target.deposit(amount); // 未同步!可能破坏不变性条件}public void deposit(int amount) {balance += amount;}
}
3. 对象逃逸
public class ThisEscape {public ThisEscape(EventSource source) {source.registerListener( // 在构造完成前发布this引用new EventListener() {public void onEvent(Event e) {doSomething(e);}});}void doSomething(Event e) { ... }
}
总结
Java 线程同步要点:
- 理解问题本质:解决共享资源访问冲突
- 选择合适的工具:从简单到复杂逐步考虑
- 遵循最佳实践:
- 优先使用并发工具包(
java.util.concurrent
) - 避免过度同步
- 最小化同步范围
- 使用线程安全的集合类
- 优先使用并发工具包(
- 考虑性能影响:
- 无锁算法 > 乐观锁 > 细粒度锁 > 粗粒度锁
- 读写分离提高并发性
- 利用现代特性:
- CompletableFuture 异步编程
- VarHandle 精细内存控制
- Virtual Threads(Project Loom)减少同步需求
正确使用同步机制能构建出安全高效的多线程应用,而错误使用可能导致性能问题或难以调试的并发缺陷。始终优先考虑使用高级并发工具而非手动实现同步逻辑。