当前位置: 首页 > ai >正文

Java并发锁相关

锁相关

​1. 什么是可重入锁?Java 中如何实现?​​
​答​:
可重入锁允许一个线程多次获取同一把锁(即递归调用时无需重新竞争锁)。

  • ​关键点​:防止死锁,避免线程因重复请求已持有的锁而阻塞。
  • ​Java 实现​:
  • synchronized:隐式支持可重入。
    public synchronized void methodA() {
    methodB(); // 可重入
    }
    public synchronized void methodB() {}
  • ReentrantLock:显式锁,通过计数器实现可重入。
    ReentrantLock lock = new ReentrantLock();
    public void method() {
    lock.lock();
    try {
    // 递归调用或其他同步代码
    } finally {
    lock.unlock();
    }
    }
    ​2. ReentrantLock 与 synchronized 的区别?​​
    特性​​synchronized​​ReentrantLock​​实现方式​JVM 关键字,自动管理锁JDK 类,需手动加锁/解锁​锁获取灵活性​不支持中断或超时支持tryLock()、lockInterruptibly()​公平性​非公平锁可选公平/非公平模式(构造函数指定)​条件变量​仅通过wait()/notify()关联一个条件支持多个Condition对象​锁释放​自动释放(退出同步块或异常)必须在finally中手动释放

​3. 什么是读写锁(ReadWriteLock)?​​
​答​:
读写锁分离读操作(共享)和写操作(独占),提升并发性能。

  • ​规则​:
  • 读锁:允许多线程同时读,但排斥写锁。
  • 写锁:独占锁,排斥其他所有读写锁。
  • ​适用场景​:读多写少(如缓存)。
  • ​Java 实现​:ReentrantReadWriteLock
    ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    public void readData() {
    rwLock.readLock().lock();
    try {
    // 读操作
    } finally {
    rwLock.readLock().unlock();
    }
    }
    public void writeData() {
    rwLock.writeLock().lock();
    try {
    // 写操作
    } finally {
    rwLock.writeLock().unlock();
    }
    }
    ​4. 死锁产生的条件?如何避免?​​
    ​死锁条件​:
    1.​互斥​:资源独占。
    2.​持有且等待​:线程持有资源并等待其他资源。
    3.​不可抢占​:资源只能由持有线程释放。
    4.​循环等待​:多个线程形成资源请求闭环。
    ​避免方法​:
  • ​破坏循环等待​:按固定顺序获取锁(如按锁对象的哈希值排序)。
    public void transfer(Account a, Account b, int amount) {
    Object firstLock = a.hashCode() < b.hashCode() ? a : b;
    Object secondLock = firstLock == a ? b : a;
    synchronized (firstLock) {
    synchronized (secondLock) {
    // 转账操作
    }
    }
    }
  • ​超时机制​:使用 tryLock() 设置超时时间。
    if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
    if (lock2.tryLock(1, TimeUnit.SECONDS)) {
    try { /* … */ } finally { lock2.unlock(); }
    }
    } finally { lock1.unlock(); }
    }
  • ​银行家算法​:动态检查资源分配是否安全(实际开发较少用)。
    ​5. 乐观锁与悲观锁的区别?​​
    类型​​原理​​实现​​适用场景​​悲观锁​假设会发生冲突,先加锁再操作synchronized、ReentrantLock写多读少​乐观锁​假设无冲突,更新时检查版本号CAS 操作(如AtomicInteger)、StampedLock读多写少

​CAS 示例​:
AtomicInteger count = new AtomicInteger(0);
public void increment() {
int oldVal, newVal;
do {
oldVal = count.get();
newVal = oldVal + 1;
} while (!count.compareAndSet(oldVal, newVal)); // CAS 更新
}
​6. synchronized 锁升级过程?​​
1.​无锁​:初始状态。
2.​偏向锁​:锁被同一线程多次访问时,记录线程 ID(避免 CAS 操作)。
3.​轻量级锁​:当多线程竞争时,通过 CAS 自旋尝试获取锁(减少阻塞)。
4.​重量级锁​:自旋超过阈值后,转为操作系统级互斥锁(线程阻塞)。

  • ​目的​:平衡性能与开销,减少直接使用重量级锁的成本。
    ​7. volatile 能否替代锁?​​
    ​答​:​不能完全替代。

  • volatile 特性​:

  • 保证可见性:修改后立即刷新到主内存。

  • 禁止指令重排序(内存屏障)。

  • ​不足​:

  • 不保证原子性(如 i++ 需配合锁或原子类)。

  • 无法实现互斥访问(如临界区保护)。
    ​适用场景​:

  • 状态标记(如 boolean flag = true)。

  • 单次读写操作(如 long、double 的 64 位写入)。
    ​8. 什么是自旋锁?​​
    ​答​:线程在获取锁失败时,不立即阻塞而是循环(自旋)重试,避免上下文切换开销。

  • ​实现​:
    public class SpinLock {
    private AtomicReference owner = new AtomicReference<>();

    public void lock() {
    Thread t = Thread.currentThread();
    while (!owner.compareAndSet(null, t)) {
    // 自旋等待
    }
    }
    public void unlock() {
    owner.set(null);
    }
    }

  • ​适用场景​:锁持有时间短、线程数少。

  • ​缺点​:长时间自旋浪费 CPU(JDK 的自旋锁有自适应策略)。
    ​9. StampedLock 是什么?​​
    ​答​:Java 8 引入的高性能读写锁,支持三种模式:
    1.​写锁​:独占锁(类似 ReentrantReadWriteLock 的写锁)。
    2.​悲观读锁​:共享锁(类似读锁)。
    3.​乐观读​:无锁操作,需验证是否有写发生。
    StampedLock sl = new StampedLock();

// 乐观读
long stamp = sl.tryOptimisticRead();
if (!sl.validate(stamp)) { // 检查期间是否有写操作
stamp = sl.readLock(); // 转为悲观读
try { /* … */ } finally { sl.unlockRead(stamp); }
}

  • ​优势​:乐观读避免锁开销,提升读性能。
    ​10. Condition 接口的作用?​​
    ​答​:配合 ReentrantLock 实现线程等待/唤醒机制,可替代 Object.wait()/notify()。
  • ​优势​:支持多个等待队列(如生产者-消费者模型)。
    java运行复制ReentrantLock lock = new ReentrantLock();
    Condition notFull = lock.newCondition(); // 队列未满条件
    Condition notEmpty = lock.newCondition(); // 队列非空条件

// 生产者
public void put(Object item) {
lock.lock();
try {
while (queue.isFull()) notFull.await(); // 等待未满
queue.add(item);
notEmpty.signal(); // 唤醒消费者
} finally { lock.unlock(); }
}

// 消费者
public Object take() {
lock.lock();
try {
while (queue.isEmpty()) notEmpty.await(); // 等待非空
notFull.signal(); // 唤醒生产者
return queue.remove();
} finally { lock.unlock(); }
}
​11. 锁消除(Lock Elision)和锁粗化(Lock Coarsening)是什么?​​

  • ​锁消除​:JIT 编译器移除不必要的锁(如局部变量锁)。
    public String concat(String s1, String s2) {
    StringBuffer sb = new StringBuffer(); // 局部变量,线程安全
    sb.append(s1).append(s2);
    return sb.toString();
    }
    // JIT 可能移除 StringBuffer 的同步操作
  • ​锁粗化​:合并多次加锁/解锁操作为一次(减少开销)。
    synchronized (obj) { /* 操作1 / }
    synchronized (obj) { /
    操作2 / }
    // 优化后 =>
    synchronized (obj) { /
    操作1+操作2 */ }
    ​12. 如何检测死锁?​​
    1.​命令行工具​:
  • jstack :查看线程堆栈,标记死锁信息。
  • jconsole:图形化检测死锁。
    2.​代码检测​:
    ThreadMXBean bean = ManagementFactory.getThreadMXBean();
    long[] threadIds = bean.findDeadlockedThreads();
    if (threadIds != null) {
    // 处理死锁
    }
    总结
  • ​基础锁​:synchronized(隐式)、ReentrantLock(显式)。
  • ​高级锁​:ReadWriteLock、StampedLock、Condition。
  • ​锁优化​:锁升级、消除、粗化。
  • ​避免死锁​:顺序加锁、超时机制。
  • ​工具​:乐观锁(CAS)、原子类、并发工具包(java.util.concurrent)。

synchronized和lock
synchronized vs Lock(以ReentrantLock为例)详解
​1. 实现机制​
特性​​synchronized​​Lock(ReentrantLock)​​​实现级别​JVM 内置关键字(字节码指令monitorenter/monitorexit)JDK API(Java 类实现)​锁状态保存​锁状态存储在对象头中(Mark Word)AQS(AbstractQueuedSynchronizer)维护锁状态

​2. 使用方式​
// ========== synchronized ==========
// 1. 方法级锁
public synchronized void syncMethod() { // }

// 2. 代码块锁
public void method() {
synchronized(this) { // 锁对象可以是任意对象
// 临界区代码
}
}

// ========== Lock ==========
private final Lock lock = new ReentrantLock();

public void lockMethod() {
lock.lock(); // 必须手动加锁
try {
// 临界区代码
} finally {
lock.unlock(); // 必须放在finally中保证释放
}
}
​3. 核心功能对比​
功能​​synchronized​​Lock​​可中断锁​❌ 不可中断✅lockInterruptibly()​尝试获取锁​❌ 不支持✅tryLock()/tryLock(timeout)​公平锁​❌ 只有非公平锁✅ 可构造公平锁(new ReentrantLock(true))​多条件变量​❌ 单条件(wait()/notify())✅ 多个Condition对象​锁绑定多条件​❌ 不能✅lock.newCondition()​锁释放​自动释放(代码块结束/异常)必须手动调用unlock()

​4. 性能差异​

  • ​Java 5 之前​:synchronized 性能远低于 Lock(线程阻塞涉及内核态切换)
  • ​Java 6 及以后​:
  • synchronized 引入锁升级机制​(偏向锁→轻量级锁→重量级锁)
  • 两者性能差距大幅缩小
  • 建议:
  • ​简单场景​ → 优先用 synchronized(简洁安全)
  • ​复杂场景​ → 用 Lock(利用高级功能)
    ​5. 锁升级过程(synchronized优化)​​
    图片代码首次获取锁其他线程竞争自旋超过阈值无锁状态偏向锁轻量级锁:自旋适应重量级锁:线程阻塞
    ​6. 条件变量对比​
    // ===== synchronized 单条件 =====
    synchronized(lockObj) {
    while(!condition) lockObj.wait(); // 只能使用一个条件
    // 业务代码
    lockObj.notifyAll();
    }

// ===== Lock 多条件 =====
private final Condition notEmpty = lock.newCondition();
private final Condition notFull = lock.newCondition();

// 生产者
lock.lock();
try {
while(queue.isFull()) notFull.await(); // 特定条件等待
// 生产操作
notEmpty.signal(); // 唤醒特定条件
} finally { lock.unlock(); }

// 消费者
lock.lock();
try {
while(queue.isEmpty()) notEmpty.await();
// 消费操作
notFull.signal();
} finally { lock.unlock(); }
​7. 适用场景总结​
场景​​推荐方案​​原因​简单同步(如单方法加锁)✅ synchronized代码简洁,自动释放需要等待超时/中断响应✅ LocktryLock(timeout)/lockInterruptibly()分组唤醒线程✅ Lock + Condition精确控制不同等待队列公平锁需求✅ ReentrantLock(true)synchronized 只能是非公平锁需要锁统计信息✅ ReentrantLock可获取等待线程数等状态简单读写分离✅ synchronized如果写操作简单,可用同步方法代替读写锁

​8. 关键差异总结​
维度​​synchronized​​Lock​​本质​JVM 原生特性Java API 接口​死锁恢复​需外部干预(重启/JDKTools)可用tryLock防止死锁​代码控制​自动管理需要显式声明lock()和unlock()​灵活性​基础同步功能提供细粒度控制能力​监控​JConsole可查看Monitor状态可编程获取锁状态(如getQueueLength())​分布式扩展​不支持可通过Redisson等实现分布式锁

💡 ​最终建议​:

  • 优先用 ​synchronized​(简洁安全,JVM持续优化)
  • 遇到以下需求时改用 ​Lock​:
    ✦ 需要等待超时/中断响应
    ✦ 分组唤醒线程
    ✦ 公平锁需求
    ✦ 需要获取锁的详细信息

锁升级过程中什么是偏向锁→轻量级锁→重量级锁?什么是自旋?

锁升级过程详解(从偏向锁→轻量级锁→重量级锁)
​1. 偏向锁(Biased Locking)​​

  • ​目的​:消除无竞争情况下的同步开销
  • ​适用场景​:单线程多次访问同步块
  • ​实现原理​:
    // 对象头存储结构
    |-------------------------------------------------------|
    | Mark Word (64 bits) |
    |-------------------------------------------------------|
    | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |
    |-------------------------------------------------------|
  • 首次获取锁时,JVM将线程ID写入对象头(biased_lock=1, lock=01)
  • 后续进入同步块时直接比对线程ID,无需CAS操作
  • ​特点​:
  • 只需一次CAS开销(首次)
  • 单线程重复访问无同步成本
  • ​示例​:
    synchronized(lock) { /* 第1次进入:CAS设置偏向锁 / }
    synchronized(lock) { /
    第2次进入:直接检查线程ID */ }
    ​2. 轻量级锁(Lightweight Locking)​​
  • ​触发条件​:第二个线程尝试获取锁(偏向锁撤销)
  • ​实现原理​:
    a.JVM在线程栈帧中创建Lock Record
    b.复制对象头到Lock Record(Displaced Mark Word)
    c.CAS尝试将对象头替换为指向Lock Record的指针
    d.成功获取锁(lock状态置为00)
  • ​锁竞争处理​:
    while (true) {
    if (尝试CAS成功) { break; }
    else if (自旋超过阈值) { 升级为重量级锁 }
    else { continue; } // 继续自旋
    }
  • ​特点​:
  • 通过自旋避免线程阻塞
  • 适合短时间持有锁的场景
  • 自旋消耗CPU但减少线程切换
    ​3. 重量级锁(Heavyweight Locking)​​
  • ​触发条件​:自旋失败或等待线程数过多
  • ​实现原理​:
    图片代码Object Header指向Monitor对象Owner:持有锁的线程EntryList:竞争队列WaitSet:等待队列
  • 依赖操作系统的互斥量(mutex)
  • 未获锁线程直接阻塞(用户态→内核态切换)
  • ​特点​:
  • 开销大(涉及上下文切换)
  • 防止CPU空转
  • 支持更复杂的同步机制

​自旋(Spinning)详解​
​1. 核心思想​

  • ​不阻塞线程,而是在循环中重试获取锁

  • 示例自旋锁实现:
    public class SpinLock {
    private AtomicReference owner = new AtomicReference<>();

    public void lock() {
    Thread t = Thread.currentThread();
    while (!owner.compareAndSet(null, t)) {
    // 空循环重试(自旋)
    }
    }

    public void unlock() {
    owner.set(null);
    }
    }
    ​2. 自旋策略​
    策略类型​​描述​​实现​​固定次数自旋​设置固定重试次数for (int i=0; i<MAX_SPIN; i++)​自适应自旋​JVM根据历史成功率动态调整自旋时间HotSpot默认策略​条件自旋​结合条件变量避免无限自旋while (!condition && spins>0)

​3. 开销分析​
场景​​CPU开销​​延迟​​适用性​自旋成功低极低✅ 理想情况自旋失败高中⚠️ 浪费CPU资源阻塞/唤醒低高⚠️ 上下文切换开销大

​4. 最佳实践​
1.​短任务优先自旋​
if (!lock.tryLock()) {
for (int i=0; i<MAX_SPIN; i++) { // 限制自旋次数
if (lock.tryLock()) return;
}
lock.lock(); // 自旋失败后阻塞
}
2.​结合条件检查​
while (true) {
if (tryAcquireLock()) break;
if (needToBlock()) {
parkThread(); // 放弃自旋
break;
}
}
3.​JVM自适应优化​

  • 历史数据:统计锁持有时间
  • 动态调整:自旋时间 ≈ 上次持有时间 * 系数
  • 平台相关:单核CPU直接禁用自旋


​关键点总结​
1.​锁升级​

  • 目标:最小化同步开销
  • 路径:无锁 → 偏向锁 → 轻量级锁 → 重量级锁
  • 不可逆:重量级锁无法降级(部分JVM支持降级但成本高)
    2.​自旋优化​
  • 目的:避免用户态→内核态切换
  • 权衡:CPU时间 vs 上下文切换开销
  • 临界点:锁持有时间 ≈ 线程切换时间 * 2
    💡 ​实战建议​:
    undefined.减少竞争:缩小同步块范围
    undefined.分离热点:读写锁分离(如ConcurrentHashMap)
    undefined.避免升级:控制锁持有时间短于自旋阈值
    undefined.监控工具:jstack -l 检查锁状态
http://www.xdnf.cn/news/20163.html

相关文章:

  • LeetCode - 202. 快乐数
  • 深度学习——数据增强(Data Augmentation)
  • HTML HTML基础(2)
  • 数控机床中,进行前瞻速度规划时,根据几何约束限制计算的拐角过渡速度
  • HTML基础(决定页面结构)
  • MQTT 与 Java 框架集成:Spring Boot 实战(一)
  • 【GEOS-Chem伴随模型第二期】GEOS-Chem Adjoint 安装与配置
  • 2025年互联网行业高含金量证书盘点!
  • leetcode 2749. 得到整数零需要执行的最少操作数 中等
  • 邪修实战系列(1)
  • 使用CI/CD部署项目(前端Nextjs)
  • SQL Server事务隔离级别
  • JavaScript 面向对象 原型和原型链 继承
  • 西嘎嘎学习-day 1
  • 栈:有效的括号
  • Dify-CHATflow案例
  • JS中的String的常用方法
  • Process Explorer 学习笔记(第三章3.2.3):工具栏与参考功能
  • 知微集:Python中的线程(三)
  • JavaScript 中的并发编程实践与误区:一次深入的探讨
  • 软考高级 — 系统规划与管理师考试知识点精要
  • 电脑活动追踪全解析:六款软件助企业实现数字化精细管理
  • whl编译命令作用解释
  • 【完整源码+数据集+部署教程】加工操作安全手套与手部检测系统源码和数据集:改进yolo11-cls
  • mysq集群高可用架构之组复制MGR(单主复制-多主复制)
  • 2025 年 8 个最佳网站内容管理系统(CMS)
  • 小迪安全v2023学习笔记(七十八讲)—— 数据库安全RedisCouchDBH2database未授权CVE
  • LeetCode 刷题【65. 有效数字】
  • 机器学习算法介绍二
  • postgresql 通过dblink实现 跨库查询