线程同步机制


知识点详细说明

线程同步机制是解决多线程环境下资源竞争和数据不一致问题的核心手段。以下是Java中常用的线程同步方式及其原理、适用场景和注意事项:


1. synchronized关键字

原理
  • 锁机制:基于对象监视器(Monitor),同一时刻只有一个线程能持有锁。

  • 锁对象

    • 实例方法:锁对象为当前实例(this)。
    • 静态方法:锁对象为类的Class对象。
    • 同步代码块:锁对象由用户指定(如Object lock)。
  • 注意:线程同步的本质是:线程排队执行就是同步机制。

  • 语法格式 :
    synchronized(必须是需要排队的这几个线程共享的对象){
    //需要同步的代码
    }

示例
// 实例方法同步
public synchronized void add() {
    count++;
}

// 静态方法同步
public static synchronized void staticAdd() {
    staticCount++;
}

// 同步代码块
public void blockAdd() {
    synchronized (lockObject) {
        count++;
    }
}
优点与缺点
优点 缺点
语法简单,自动释放锁。 功能单一(不可中断、不支持超时)。
无需手动管理锁的获取与释放。 锁粒度粗时性能较差。
适用场景
  • 简单的代码块同步,如单方法内的共享资源保护。

2. ReentrantLock显式锁

原理
  • 可重入锁:允许同一线程多次获取同一把锁。
  • 高级功能:支持公平锁、可中断锁、超时尝试获取锁、条件变量(Condition)。
示例
private final ReentrantLock lock = new ReentrantLock();

public void add() {
    lock.lock();
    try {
        count++;
    } finally {
        lock.unlock();
    }
}
优点与缺点
优点 缺点
支持灵活的锁控制(如超时)。 需手动释放锁,易遗漏导致死锁。
可配合Condition实现精准唤醒。 代码复杂度较高。
适用场景
  • 需要复杂锁逻辑的场景,如限时等待、条件分支唤醒。

3. volatile关键字

原理
  • 可见性:强制线程从主内存读取变量,修改后立即写回主内存。
  • 禁止指令重排序:通过内存屏障(Memory Barrier)保证代码执行顺序。
示例
private volatile boolean flag = false;

public void setFlag() {
    flag = true; // 对其他线程立即可见
}
优点与缺点
优点 缺点
轻量级,无锁竞争。 仅保证可见性,不保证原子性。
适合单写多读场景。 无法解决复合操作(如i++)的线程安全。
适用场景
  • 状态标志位(如开关控制),单例模式的双重检查锁。

4. 原子类(AtomicInteger等)

原理
  • CAS操作:通过CPU指令(Compare-And-Swap)实现无锁原子操作。
示例
private AtomicInteger count = new AtomicInteger(0);

public void add() {
    count.incrementAndGet();
}
优点与缺点
优点 缺点
高性能(无锁竞争)。 仅适用于单一变量的简单操作。
无需显式同步。 无法处理复杂逻辑的原子性。
适用场景
  • 计数器、状态标记等简单原子操作。

5. 线程同步工具类

(1) CountDownLatch
  • 原理:通过计数器等待多个线程完成。
  • 示例:主线程等待所有子线程完成:
    CountDownLatch latch = new CountDownLatch(3);
    // 子线程中
    latch.countDown();
    // 主线程中
    latch.await();
    
(2) CyclicBarrier
  • 原理:线程到达屏障点后等待其他线程,全部到达后继续执行。
  • 示例:多阶段任务协同:
    CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("所有线程到达屏障"));
    // 线程中
    barrier.await();
    
(3) Semaphore
  • 原理:控制同时访问资源的线程数量。
  • 示例:限制数据库连接池并发数:
    Semaphore semaphore = new Semaphore(10);
    semaphore.acquire(); // 获取许可
    semaphore.release(); // 释放许可
    

6. 线程封闭(ThreadLocal

原理
  • 线程本地存储:每个线程拥有独立的变量副本,避免共享。
  • 示例:保存用户会话信息:
    private static ThreadLocal<User> userHolder = new ThreadLocal<>();
    userHolder.set(currentUser); // 当前线程存储
    User user = userHolder.get(); // 当前线程获取
    
优点与缺点
优点 缺点
彻底避免线程安全问题。 可能引发内存泄漏(需及时清理)。
适合线程间数据隔离的场景。 不适用于跨线程数据共享。

同步机制对比总结

机制 锁类型 性能 适用场景
synchronized 悲观锁 中等 简单同步块或方法
ReentrantLock 悲观锁 中高 复杂锁逻辑(超时、条件变量)
volatile 无锁(可见性) 状态标志位
原子类 无锁(CAS) 极高 计数器、简单变量更新
工具类 依赖具体实现 可变 多线程协同(如计数等待、限流)
ThreadLocal 无锁(线程封闭) 线程隔离数据(如数据库连接)

记忆方法

  • 口诀
    同步锁,原子类,volatile保可见;工具协同ThreadLocal,各司其职解难题。
  • 对比记忆
    • synchronized像公共电话亭(一次一人使用)。
    • ReentrantLock像智能门锁(可设置密码、超时)。
    • ThreadLocal像个人储物柜(各自独立,互不干扰)。

最佳实践

  1. 优先使用无锁方案:如原子类、ThreadLocal
  2. 减小锁粒度:同步代码块 > 同步方法,锁对象分离(如细粒度锁)。
  3. 避免锁嵌套:预防死锁,按固定顺序获取锁。
  4. 及时释放资源ReentrantLock需在finally中解锁,ThreadLocal使用后调用remove()

你可能感兴趣的:(java知识点,java,开发语言)