Java中的线程同步全面讲解

 更新时间:2025年07月30日 08:29:54   作者:六七_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, 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 规则

  1. 程序顺序规则
  2. 监视器锁规则
  3. volatile变量规则
  4. 线程启动规则
  5. 线程终止规则
  6. 中断规则
  7. 终结器规则
  8. 传递性
// 正确同步示例
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 线程同步要点:

  1. 理解问题本质:解决共享资源访问冲突
  2. 选择合适的工具:从简单到复杂逐步考虑
  3. 遵循最佳实践
    • 优先使用并发工具包(java.util.concurrent
    • 避免过度同步
    • 最小化同步范围
    • 使用线程安全的集合类
  4. 考虑性能影响
    • 无锁算法 > 乐观锁 > 细粒度锁 > 粗粒度锁
    • 读写分离提高并发性
  5. 利用现代特性
    • CompletableFuture 异步编程
    • VarHandle 精细内存控制
    • Virtual Threads(Project Loom)减少同步需求

正确使用同步机制能构建出安全高效的多线程应用,而错误使用可能导致性能问题或难以调试的并发缺陷。始终优先考虑使用高级并发工具而非手动实现同步逻辑。

到此这篇关于Java中线程同步的文章就介绍到这了,更多相关java线程同步内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • mybatis查询语句揭秘之参数解析

    mybatis查询语句揭秘之参数解析

    这篇文章主要给大家介绍了关于mybatis查询语句之参数解析的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用mybatis具有一定的参考学习价值,需要的朋友们下面来一起学习学习吧
    2019-04-04
  • 关于feign接口动态代理源码解析

    关于feign接口动态代理源码解析

    这篇文章主要介绍了关于feign接口动态代理源码解析,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • SpringBoot项目jar依赖问题报错解析

    SpringBoot项目jar依赖问题报错解析

    本文主要介绍了Spring Boot项目中常见的依赖错误类型、报错内容及解决方法,依赖冲突包括类找不到、方法找不到、类型转换异常等,本文给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧
    2025-12-12
  • JAVA文件读写例题实现过程解析

    JAVA文件读写例题实现过程解析

    这篇文章主要介绍了JAVA文件读写例题实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-06-06
  • RocketMq深入分析讲解两种削峰方式

    RocketMq深入分析讲解两种削峰方式

    当上游调用下游服务速率高于下游服务接口QPS时,那么如果不对调用速率进行控制,那么会发生很多失败请求,通过消息队列的削峰方法有两种,这篇文章主要介绍了RocketMq深入分析讲解两种削峰方式
    2023-01-01
  • Java基础之switch分支结构详解

    Java基础之switch分支结构详解

    这篇文章主要介绍了Java基础之switch分支结构详解,文中有非常详细的代码示例,对正在学习java的小伙伴们有很大的帮助,需要的朋友可以参考下
    2021-05-05
  • Java全面细致讲解==和equals的使用

    Java全面细致讲解==和equals的使用

    这篇文章主要介绍了Java中==和equals()的区别,,==可以使用在基本数据类型变量和引用数据类型变量中,equals()是方法,只能用于引用数据类型,需要的朋友可以参考下
    2022-05-05
  • Hibernate处理多对多关系的实现示例

    Hibernate处理多对多关系的实现示例

    本文介绍了Hibernate中实现多对多关系的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2026-01-01
  • Java中@DateTimeFormat @JsonFormat失效原因及测试填坑

    Java中@DateTimeFormat @JsonFormat失效原因及测试填坑

    本文主要介绍了Java中@DateTimeFormat @JsonFormat失效原因及测试填坑,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2023-06-06
  • Java中的值传递以及引用传递和数组传递详解

    Java中的值传递以及引用传递和数组传递详解

    这篇文章主要介绍了Java中的值传递以及引用传递和数组传递详解,Java不允许程序员选择按值传递还是按引用传递各个参数,就对象而言,不是将对象本身传递给方法,而是将对象的的引用或者说对象的首地址传递给方法,引用本身是按值传递的,需要的朋友可以参考下
    2023-07-07

最新评论