synchronized 和 lock 有什么区别?

如果在开发中,你需要对某个共享资源加锁,但是你希望在这个获取锁的过程中,最多只等5秒钟,如果超时了拿不到就算了,直接去执行降级逻辑。这时候,你应该怎么做?用原生的synchronized关键字可以吗?

不行的,synchronized 只能死等,拿不到锁就会一直阻塞在那。

又或者,一个线程正在苦苦等待获取锁,但业务上突然发生了变化(比如用户取消了请求),你想中断这个线程的等待,让它别再干了。用 synchronized 可以响应这个中断吗?

同样也是不行的,它对外部的中断信号完全无动于衷。

这就是我们在高并发编程中经常遇到的痛点:synchronized 作为 JVM 内置的锁,用起来确实简单,但它太死板了。出于灵活性的考虑,我们需要一种更可控、更具弹性的加锁和释放锁机制。

这也是为什么 Java 引入了今天要聊的:public interface Lock。哪怕到了Java 17,虽然 JVM 对 synchronized 做了无数次底层的锁膨胀优化,但在遇到需要精细化控制并发的场景时,Lock 依然是不可替代的利器。

作为 API 层面的锁,Lock 把控制权交给了开发者。我们来看看它的几个核心特性,以及最简单的代码实现(通常我们都会使用它的经典实现类 ReentrantLock)。

基础加锁与释放(替代 synchronized 的基本操作)

和 synchronized 自动释放锁不同,Lock 最大的特点就是:加了锁,就必须手动释放。所以代码结构永远是配合 try-finally 出现的。

Java 复制代码
Lock lock = new ReentrantLock();

lock.lock(); // 阻塞式获取锁
try {
    // 操作共享资源
} finally {
    lock.unlock(); // 铁律:必须在 finally 中释放,防止死锁
}

尝试非阻塞获取锁 / 带超时的获取(解决死等问题)

这是 Lock 极大提升灵活性的地方。你可以尝试去拿锁,拿到就干活,拿不到就走人;或者像开头说的那样,最多等一段时间。

同时,这也是提升性能的一种机制,不会死等,白白浪费资源。

Java 复制代码
// 尝试获取,最多等待 5 秒
if (lock.tryLock(5, TimeUnit.SECONDS)) {
    try {
        // 获取到了锁,执行核心业务
    } finally {
        lock.unlock();
    }
} else {
    // 等了5秒还是没拿到,执行降级或快速失败逻辑
    System.out.println("系统繁忙,请稍后再试");
}

响应中断的加锁(解决无法打断的问题)

如果一个线程A在等待锁的过程中,被其他线程(比如主线程)调用了A线程的 interrupt(),lockInterruptibly() 会立刻抛出 InterruptedException,让你有机会终止当前任务,而不是变成一个永远挂起的僵尸线程。

这块不太容易理解,我们写一个完整的例子来协助理解这个知识点。

Java 复制代码
public static void main(String[] args) throws InterruptedException {
    Lock lock = new ReentrantLock();

    // 1. 主线程先直接把锁占了
    lock.lock();
    System.out.println("主线程:我已经拿到了锁,我先占着不放。");

    // 2. 此时,开启一个工作线程A去干活
    Thread workerThreadA = new Thread(() -> {
        System.out.println("工作线程:我准备去获取锁,我可以等,但也接受中断...");
        try {
            // 因为锁在主线程手里,工作线程执行到这里会被直接阻塞(卡住)
            lock.lockInterruptibly(); 
            
            try {
                System.out.println("工作线程:终于拿到锁了,开始干活!");
            } finally {
                lock.unlock();
            }
        } catch (InterruptedException e) {
            // 4. 工作线程正在等锁的过程中,收到了来自其他线程的中断信号,抛出异常,并醒过来。
            System.out.println("工作线程:等锁的过程中,突然被叫醒了,准备退出!");
            // 恢复中断标志位,这是一种良好的并发编程习惯
            Thread.currentThread().interrupt(); 
        }
    });

    workerThreadA.start();

    // 主线程假装处理了一些业务,耗时 2 秒。在这2秒内,workerThread一直在阻塞等锁
    Thread.sleep(2000); 

    // 3. 业务上发生了变化,主线程决定打断工作线程的等待
    System.out.println("主线程:业务有变,不需要工作线程继续等锁干活了,我现在就去中断它!");
    workerThreadA.interrupt(); // 关键动作:由主线程调用了工作线程的interrupt()

    // 主线程最后释放自己的锁
    lock.unlock();
}

这段代码几个要点要说明一下:

  • 是谁在调用 **interrupt()****?是**主线程(或者说是脱离了工作线程之外的其他线程)。在这里,工作线程是执行者,主线程是控制者。

  • **具体的业务场景是什么样的?**实际开发中,这个主线程往往对应的是接收外部指令的线程。比如,你在前端页面点了一个生成报表的按钮,后台启动了一个工作线程去拿锁、取数据。过了两秒钟,你觉得太慢了,点了一下取消。这时候,后台接收取消请求的线程,就会去找到刚才那个工作线程,调用它的interrupt()。

    如果那个工作线程正在用synchronized死等数据库连接池的锁,它是停不下来的;但如果用的是lockInterruptibly(),它就会立刻抛出异常,结束自己,不浪费系统资源。

公平锁与非公平锁的选择

顺带一提,synchronized 永远是非公平锁(大家涌入进来一起抢,谁抢到算谁的)。而 Lock 允许你选择是否要排队。虽然绝大多数时候为了吞吐量我们用非公平锁,但有些强顺序要求的业务,公平锁就派上用场了。

Java 复制代码
// 传入 true,这就是一把公平锁,先来后到,按排队顺序拿锁
Lock fairLock = new ReentrantLock(true);

说说synchronized 和 Lock 的区别,这是一道经典的Java并发编程面试题,下面我把注意区别列成一个表格,方便你复习。

对比维度 synchronized Lock(ReentrantLock)
实现层面 JVM 关键字,monitorenter/monitorexit 字节码 JDK 层面的接口和类
锁的释放 自动释放 手动释放,必须在 finally 中 unlock()
超时获取 不支持 tryLock(timeout, unit)
响应中断 不支持 lockInterruptibly()
非阻塞获取 不支持 tryLock()
公平锁 不支持,只有非公平锁 支持,构造方法可指定
可重入 是 是(ReentrantLock 的名字就说明了一切)

那如果面试官问你,两者应该如何选择呢? 怎么回答?

你可以简单的这么说,synchronized是粗粒度的,如果要细粒度的做控制,可以使用Lock。

如果场景很简单,只是简单的互斥,就用:

Java 复制代码
synchronized (lock) { 
// 修改共享数据 
}

可以了,且锁会自动释放。

如果需要超时获取、响应中断、非阻塞尝试获取,或者需要Condition这样的机制,就可以考虑 Lock。

相关推荐
考研保研资料分享1 小时前
普通211核专业保研经验:清华预推免录取与北大、西交面试复盘
面试·职场和发展
m0_587383001 小时前
无人自助拼豆系统的全栈开发方案
java·后端·struts·小程序·架构
临期冰淇淋1 小时前
MTK平台 安卓16 GMS报错修改
android·java·前端
人道领域1 小时前
【面试八股速通篇01】锁与并发工具
java·开发语言·面试·c#
Wang's Blog1 小时前
Java 项目实战: 交友项目 - Dubbo 入门案例全流程复盘
java·dubbo·交友
Profile排查笔记1 小时前
浏览器多开怎么设置独立 IP?Profile、代理与核验流程
前端·人工智能·后端·自动化
小七在进步2 小时前
初识C++中的模板
java·开发语言·c++
tianbin9112 小时前
中小团队大模型训练落地实战:如何借助API网关降低工程负担
网络·后端
Wang's Blog2 小时前
Java 项目实战: 交友项目 - SpringCloud 整合 Dubbo 的需求分析与模块规划
java·spring cloud·交友