Java中的线程同步全面讲解
作者:六七_Shmily
线程同步指的是在多线程环境下,通过某种机制来协调多个线程对共享资源的访问,确保在同一时刻只有一个线程能够访问共享资源,从而避免数据不一致和其他并发问题,这篇文章主要介绍了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,AtomicLongAtomicReferenceAtomicBooleanLongAdder(高并发计数器)
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)减少同步需求
正确使用同步机制能构建出安全高效的多线程应用,而错误使用可能导致性能问题或难以调试的并发缺陷。始终优先考虑使用高级并发工具而非手动实现同步逻辑。
到此这篇关于Java中线程同步的文章就介绍到这了,更多相关java线程同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
