Java多线程安全深度解析

最近正在复习Java八股,所以会将一些热门的八股问题,结合ai与自身理解写成博客便于记忆
在这里插入图片描述
本文将以上问题为参考解析多线程

一、线程安全核心概念

1. 线程安全定义

线程安全指当多个线程访问某个类时,这个类始终能表现出正确的行为,无需额外的同步协调。线程安全的三个核心要素:

  • 原子性:操作不可中断
  • 可见性:修改立即对其他线程可见
  • 有序性:程序执行顺序符合预期

2. 线程安全实现手段

手段实现方式适用场景
不可变对象final修饰配置类、常量类
线程封闭ThreadLocal连接管理、日期格式化
同步控制synchronized/Lock共享资源访问
并发容器ConcurrentHashMap高频读写的集合场景

二、synchronized深度解析

1. 实现原理

字节码层面

// 同步代码块
monitorenter
// 临界区代码
monitorexit

// 同步方法
ACC_SYNCHRONIZED方法标志

JVM实现

  • 每个对象关联一个Monitor(监视器锁)
  • 通过对象头中的Mark Word实现锁状态记录

2. 锁升级过程

无锁 -> 偏向锁 -> 轻量级锁 -> 重量级锁

升级触发条件

  • 偏向锁:第一个线程访问时,记录线程ID
  • 轻量级锁:发生竞争时,通过CAS自旋尝试获取
  • 重量级锁:自旋超过阈值(默认10次),升级为OS级互斥锁

对象头结构变化

锁状态存储内容
无锁对象哈希码、分代年龄
偏向锁持有线程ID、Epoch、分代年龄
轻量级锁指向栈中锁记录的指针
重量级锁指向互斥量的指针

三、CAS机制详解

1. CAS原理

比较并交换(Compare And Swap)的原子操作:

// CPU指令级实现
bool CAS(int* ptr, int expected, int newVal) {
    if(*ptr == expected) {
        *ptr = newVal;
        return true;
    }
    return false;
}

2. 应用场景

  • Atomic类:AtomicInteger等原子类
  • 并发容器:ConcurrentHashMap的节点操作
  • 自旋锁:ReentrantLock的底层实现

3. 优缺点分析

优势

  • 无锁并发,性能高
  • 避免线程上下文切换

缺陷

  • ABA问题(通过版本号解决)
  • 自旋消耗CPU
  • 只能保证单个变量原子性

四、ReentrantLock对比

1. 核心特性对比

特性synchronizedReentrantLock
实现机制JVM内置JDK实现(AQS)
锁获取方式自动获取释放必须手动lock/unlock
可中断性不可中断支持lockInterruptibly()
公平性非公平可配置公平/非公平
条件变量单一wait/notify支持多个Condition
性能Java6后优化相当高竞争时表现更好

2. 使用示例

ReentrantLock lock = new ReentrantLock(true); // 公平锁
Condition condition = lock.newCondition();

lock.lock();
try {
    while(!conditionMet) {
        condition.await();
    }
    // 业务逻辑
    condition.signal();
} finally {
    lock.unlock();
}

五、AQS框架揭秘

1. AQS核心结构

// 关键属性
volatile int state; // 同步状态
Node head;         // 等待队列头节点
Node tail;         // 等待队列尾节点

// 节点结构
class Node {
    volatile int waitStatus;
    volatile Node prev, next;
    volatile Thread thread;
    Node nextWaiter; // 条件队列专用
}

2. 实现原理

获取锁流程

  1. 尝试CAS修改state
  2. 失败后加入CLH队列
  3. 进入自旋或阻塞状态
  4. 被前驱节点唤醒后重新尝试

释放锁流程

  1. CAS修改state
  2. 唤醒后继节点

六、乐观锁与悲观锁

1. 对比分析

维度悲观锁乐观锁
实现方式synchronized/ReentrantLockCAS/版本号
适用场景写多读少读多写少
并发性能低(阻塞)高(无阻塞)
冲突处理等待回滚重试
典型实现数据库行锁MVCC/Atomic类

2. 数据库乐观锁实现

-- 基于版本号
UPDATE products 
SET stock = stock - 1, version = version + 1 
WHERE id = 100 AND version = 5;

-- 基于时间戳
UPDATE orders 
SET status = 'PAID', update_time = NOW() 
WHERE id = 200 AND update_time = '2023-01-01 10:00:00';

七、生产环境实践

1. 锁选择建议

  • 低竞争场景:synchronized(简洁高效)
  • 高竞争场景:ReentrantLock(灵活控制)
  • 读多写少:ReadWriteLock/StampedLock
  • 分布式环境:Redis/Zookeeper分布式锁

2. 性能优化技巧

// 减小锁粒度
ConcurrentHashMap<String, Lock> lockMap = new ConcurrentHashMap<>();
Lock lock = lockMap.computeIfAbsent(key, k -> new ReentrantLock());
lock.lock();
try {
    // 操作特定key的数据
} finally {
    lock.unlock();
}

// 锁分段技术(Java7 ConcurrentHashMap实现)
final Segment<K,V>[] segments;

八、常见问题解答

Q1:synchronized能否保证可见性?

答案:可以。根据JMM规范:

  1. 解锁前必须把变量写回主内存
  2. 加锁后必须从主内存读取最新值

Q2:如何解决CAS的ABA问题?

解决方案

  1. 使用AtomicStampedReference(带版本号)
  2. 使用AtomicMarkableReference(带标记位)
AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(100, 0);
ref.compareAndSet(100, 101, stamp, stamp + 1);

Q3:AQS为什么用CLH队列?

设计优势

  1. 无锁队列管理(CAS维护)
  2. 高效唤醒机制(前驱节点负责唤醒)
  3. 减少缓存同步(局部自旋)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值