Java线程同步案例如何实现:从基础到实战的完整指南
目录导读
- 为什么需要线程同步?
- Java同步机制核心概念
- 使用synchronized实现银行转账
- ReentrantLock实现库存扣减
- CountDownLatch模拟并发赛跑
- CyclicBarrier实现分批汇总
- 常见问题与最佳实践
- 总结与问答
为什么需要线程同步?
在多线程环境中,线程同步是保证数据一致性和程序正确性的关键机制,例如两个线程同时对一个余额为100的账户执行取款80的操作,如果不同步,结果可能变成60(正确应为20),Java提供多种同步方案来解决这类问题。

Q: 不进行同步会有什么后果?
A: 会出现竞态条件(Race Condition),导致数据污染、程序逻辑混乱,甚至引发死锁或系统崩溃。
Java同步机制核心概念
Java线程同步主要通过以下方式实现:
- synchronized关键字:内置锁,自动释放,适用于简单场景
- Lock接口实现类(如ReentrantLock):更灵活,支持超时、中断锁
- 并发工具类(如Semaphore、CountDownLatch、CyclicBarrier)
- volatile变量:保证可见性,但不保证原子性
- 原子类(如AtomicInteger):实现无锁同步
核心原则:保证互斥访问(同一时刻只有一个线程能执行临界区代码)和可见性(修改后的值对其他线程立即可见)。
案例一:使用synchronized实现银行转账
public class BankAccount {
private int balance = 1000;
// 同步方法:保证取款过程原子性
public synchronized boolean withdraw(int amount) {
if (balance >= amount) {
try { Thread.sleep(10); } catch (InterruptedException e) {} // 模拟延迟
balance -= amount;
return true;
}
return false;
}
public int getBalance() { return balance; }
public static void main(String[] args) throws InterruptedException {
BankAccount account = new BankAccount();
Thread t1 = new Thread(() -> account.withdraw(800));
Thread t2 = new Thread(() -> account.withdraw(500));
t1.start(); t2.start();
t1.join(); t2.join();
System.out.println("最终余额: " + account.getBalance()); // 正确结果应为200
}
}
Q: synchronized修饰方法时锁住的是什么?
A: 如果方法是实例方法,锁是当前对象(this);如果是静态方法,锁是类的Class对象。
案例二:ReentrantLock实现库存扣减
当synchronized无法满足需求时(如需要尝试获取锁、中断等待),可使用ReentrantLock:
import java.util.concurrent.locks.ReentrantLock;
public class InventoryService {
private int stock = 100;
private final ReentrantLock lock = new ReentrantLock();
public boolean reduceStock(int quantity) {
lock.lock();
try {
if (stock >= quantity) {
stock -= quantity;
return true;
}
return false;
} finally {
lock.unlock(); // 必须在finally中释放
}
}
public static void main(String[] args) {
InventoryService inventory = new InventoryService();
Runnable task = () -> {
int myQuantity = (int)(Math.random() * 20) + 1;
if (inventory.reduceStock(myQuantity)) {
System.out.println(Thread.currentThread().getName() + " 扣减成功,数量=" + myQuantity);
} else {
System.out.println(Thread.currentThread().getName() + " 库存不足!");
}
};
for (int i = 0; i < 10; i++) new Thread(task).start();
}
}
Q: 使用Lock比synchronized好在哪?
A: Lock可以尝试获取锁(tryLock)、设置超时时间、响应中断,还支持公平锁和读写锁分离,性能在高并发下更优。
案例三:CountDownLatch模拟并发赛跑
用于等待多个线程全部到达某个点后再统一执行:
import java.util.concurrent.CountDownLatch;
public class Race {
private static final int RUNNER_NUM = 5;
private static CountDownLatch startLatch = new CountDownLatch(1);
private static CountDownLatch finishLatch = new CountDownLatch(RUNNER_NUM);
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= RUNNER_NUM; i++) {
new Thread(() -> {
try {
startLatch.await(); // 所有线程等待发令枪
System.out.println(Thread.currentThread().getName() + " 起跑!");
Thread.sleep((long)(Math.random() * 1000));
System.out.println(Thread.currentThread().getName() + " 到达终点!");
finishLatch.countDown();
} catch (InterruptedException e) { e.printStackTrace(); }
}, "运动员" + i).start();
}
System.out.println("裁判员:预备——");
Thread.sleep(500);
startLatch.countDown(); // 发令枪响
finishLatch.await(); // 等待所有运动员完成
System.out.println("所有运动员都完成比赛!");
}
}
Q: CountDownLatch与CyclicBarrier的区别?
A: CountDownLatch是计数一次,不可重用;CyclicBarrier可循环使用,且支持在到达屏障时执行额外动作。
案例四:CyclicBarrier实现分批汇总
模拟多线程分阶段计算并汇总结果:
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class DataSummary {
private static final int THREAD_COUNT = 3;
private static CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () ->
System.out.println("---所有线程完成本轮汇总,开始下一轮---"));
public static void main(String[] args) {
for (int i = 0; i < THREAD_COUNT; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName() + " 处理数据1...");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 处理数据2...");
barrier.await();
System.out.println(Thread.currentThread().getName() + " 完成最终汇总!");
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}, "工作线程-" + i).start();
}
}
}
Q: 如果某个线程在barrier.await()时抛异常会怎样?
A: 该线程会进入BrokenBarrierException状态,其他线程也会收到此异常,整个屏障将不可用。
常见问题与最佳实践
死锁预防:确保多个锁的获取顺序一致,或使用tryLock超时机制。
性能调优:
- 减小同步块范围(只锁关键代码)
- 优先使用偏向锁(JVM自动优化)
- 高并发场景用Lock替代synchronized
工具使用建议:
- 读多写少:使用ReadWriteLock
- 限制并发数:使用Semaphore
- 无锁编程:使用Atomic类或CAS操作
Q: volatile能替代synchronized吗?
A: 不能,volatile只保证可见性,不保证原子性,例如对volatile变量的i++操作仍是线程不安全的。
总结与问答
本文通过银行转账、库存扣减、并发赛跑、数据汇总四个实战案例,全面展示了Java线程同步的synchronized、Lock、CountDownLatch、CyclicBarrier等核心实现方式,选择合适的同步机制需要综合考虑业务场景、并发量和性能需求。
问答精选:
Q1: 如果使用synchronized修饰整个方法,但方法内只有少量代码需要同步怎么办?
A: 应该改为在方法内部使用synchronized代码块,只锁住需要保护的代码片段,提高并发性能。
Q2: 多个线程访问同一个对象的同步和非同步方法,会相互阻塞吗?
A: 不会,同步方法需要获取对象锁,非同步方法不需要,它们可以同时执行。
Q3: 如何解决ABA问题?
A: 使用带版本号的原子操作类(如AtomicStampedReference),通过对比版本号来避免ABA问题。
Q4: 在高并发环境下,哪种同步方式性能最高?
A: 无锁同步(如AtomicInteger)性能高于锁,但只能处理单一变量;公平锁性能低于非公平锁,但可防止线程饥饿。
通过以上案例和问答,相信您已掌握Java线程同步的核心实现技巧,在实际项目中,请务必结合压测工具验证同步逻辑的正确性和性能表现。