
图1是本文的封面, 它明确了题目的具体内容, 同时也揭示了以“对比”和“选型”为核心的主线。首先交代一下文章的属性, 这是一篇对经典题目的解析。这道题选自我的本地的历史面试资料, 具体的来源情况在文章末尾会有说明, 它与任何一家公司目前有没有招聘需求都没有关系。「 和 Lock 的区别」是 Java 高并发改编程面试中经常被问到的必考题, 每个人都能够背诵出其中的两三条内容, 但是面试官真正想要看到的不是你背诵了多少内容, 而是你能不能把这两种锁的差异整理成一张具有清晰结构对比表格, 并且最终落实到「在具体什么使用场景下面选择使用哪种方案」这样的实际问题决策之中上来。请你试着去想象一下面试的具体现场, 面试官会向你提问, 具体的内容是关于「和 」这两个事物的使用情况, 他会让你描述它们之间的区别, 然后要求你再详细说明在你的项目实际运作之中应当怎么样去进行选择和决策。先把题看清楚, 然后才能继续往下走。把这个问题拆开来看, 分成两个层面, 一个是差异在哪另一个是怎么选型。说到差异的时候, 我们千万不要零零散散地去死记硬背那些知识点, 而是要按照六个维度来进行对照讲解, 这六个维度包括出身这一项、用法这一项、中断处理这一项、超时机制这一项、公平性控制这一项, 以及条件队列这一块关于怎么选择使用的问题, 总结一句话就是决策口诀, 同时还需要明确一个大前提, 也就是通常情况下默认去用前一个, 但是如果遇到了需要锁定工具独占这种特殊能力的场景, 这时候再去换成后一个。第二步六个维度对照。这个词汇是JVM内置的关键字, 它对每个对象自带的监视器进行锁定Lock则是JDK中的一组接口, 这些接口代表着具体的实现方式, 官方文档在开篇就明确指出了其定位, 即Lock的实现能够提供比同步方法和语句范围更加广泛的锁操作, 一方面属于语言的特性层面, 另一方面只是普通的API应用, 这种差异从根本上决定了两者后续所表现出的所有不同之处。关于这些内容的用法情况是这样的, 当进入代码块的时候会自动加上锁, 而出这个代码块的时候, 哪怕是遇到异常情况发生也会自动释放掉这种锁状态。相对而言, Lock 这个东西并没有提供类似这样的基于代码块结构的保护机制, 所以对于它, 官方文档方面明确地指出了必须去遵循的那种使用习惯做法, 也就是说要先执行 lock() 这个操作, 然后再紧接着去开始一个 try 语句块, 然后把那些实际的业务处理代码全部放到 try 关键字对应的大括号里面去, 最后再把释放锁的相关逻辑代码放在大括号里来确保完成清理工作。自动释放会带来省心的效果, 手动释放会换来灵活的特性。锁能够被获取和释放的顺序是任意的, 其作用域也是不同的。比如在链式遍历时使用手递手的锁定方法, 这种情况是那种结构所无法做到的。发生了中断的情况以及超时的情况。当线程去获取一把锁的时候, 如果拿不到这把锁它就会一直在那里等待着, 而且这个过程是不可被中断的而 Lock 这种机制提供了那些功能, 其中包括了可以在等待的时候响应中断请求的功能, 还包括了如果拿不到锁就会立刻返回 false 这个结果的功能, 另外还有带有时候参数的等待方法, 比如传入一个long类型的数字和时间单位用来设置限时等待的, 如果超时了没有拿到锁也会返回 false 这个结果的。在需要进行死锁排查的工作场合里以及在需要执行服务降级操作的场景之中, 这三种特性或者三种手段是非常关键的, 简直就是救命的本领。这是一种公平性的体现。它并不是绝对公平的, 因为用户没有选择的余地, 其构造器会接受一个可选的公平参数作为输入。根据官方文档的说明, 在存在竞争的情况下, 公平锁倾向于把锁授予那些等待时间最长的线程, 这样做能够杜绝线程出现饿死的现象, 但是其代价是整体吞吐量明显降低。需要特别注意文档中提到的一个例外情况: 如果调用无参构造函数来创建实例, 那么就不讲求公平性, 锁处于空闲状态时大家就抢着用, 如果想要遵守规矩, 就请使用带有参数的特定写法0, .。所谓的条件队列就是用来配合那些等待操作的, 具体来说的话是和一个对象只有一个等待队列这样的情况有关, 在这种情况下你没有办法去控制到底要唤醒谁。但是如果是采用锁这种机制的话它是可以挂靠多个条件的, 官方文档说明里面返回的是绑定在该锁上的条件实例, 这个实例的作用就在于让等待以及唤醒的操作能够分属于不同的队列来进行, 在典型的生产者和消费者模型里面会看到有非空以及非满这两个条件是分开来唤醒的, 而做到这一点靠的就是我们上面提到的那个东西。第三步, 有一个共同点, 而这个共同点是容易出现忽略情况的。在讲完全部差异之后, 需要主动补上一句: 官方文档要求所有 Lock 实现必须提供与内置监视器锁完全相同的内存同步语义, 成功的 lock 操作与获取监视器的内存效果和监视器的 Lock/ 动作一致。也就是说, 两者的可见性保证是相同的, 差异只在功能与灵活性上, 不在“哪个更正确”这个问题上。图2展示了一个包含六个维度的对比表格, 同时还配有一个用于辅助进行选型决定的决策条条。到了进行第四步选择的时候, 就需要做出关键的决策了。口诀是默认选择。它的写法比较简单, 不容易出现遗漏情况并且 JVM 始终对它进行持续优化, 所以在绝大多数同步场景里, 它就是最完美的解决方案。那么在什么时候才需要更换为 Lock 呢?如果有四种需要出现其中一种情况, 那就是 Lock 大显身手的时候。第一类需要有超时等待机制, 也就是说如果获取锁失败就需要执行降级操作第二类需要有可中断处理机制以便支持任务取消这类语义第三类是需要公平锁, 目的是为了实现严格的先进先出规则来防止进程饿死现象第四类是需要具备多个条件队列, 从而实现更加细致的等待与唤醒操作。反过来说, 仅仅只是因为「Lock听起来更高级」就进行更换, 这是把风险引进来的一个做法。这是一份容易出错的重点清单内容。第一点, 你明明用了 Lock 这个接口, 却从来不写 try 或者 代码块来确保释放锁, 一旦代码执行过程中跑出异常, 异常就一直往上抛, 根本出不去, 这时候锁永远都回不来了, 这是使用 Lock 时在线上环境里最容易发生的经典事故。第二点, 许多人心里以为公平锁运行起来既快又公平, 但是官方文档里面讲得明明白白, 它在大多数的情况下跑得比非公平锁明显慢很多, 所谓的公平, 其实就是用吞吐量作为代价换来的。第三点, 有人认为某些特定的锁机制性能一定很差, 这个观点是错误的因为 JVM 虚拟机对它们做了非常多的优化工作, 比如锁消除、锁升级等一系列操作都存在, 所以说它慢的话, 那是已经过时的结论了。第四点, 虽然你能够背诵那六条差异之处, 但是却无法回答出具体的选型方案。因为面试官所关注的落脚点, 永远是你的实际应用场景究竟应该选用哪一个选项, 以及需要阐述采用该选项的特定理由是什么。。回到面试现场, 关于答题的节奏, 这里给大家一些建议。第一步是先抛出选型的口诀, 这个口诀的大致含义是: 默认情况使用, 只有满足四个特定条件时才去选择Lock。第二步, 按照六个维度来展开对比。第三步, 补充说明两者在内存语义上是相同的这一点。最后呢, 举出你自己项目里一个使用了锁的真实场景来做收尾。这样做的时候, 既有结构感, 又有取舍的考量, 还有实例支撑, 那么这个题目你就回答得很全面了。在推理服务以及数据管道中, 针对连接池和缓冲队列进行设计时, 核心议题之一在于锁的选型。假如面试官深入追问, “在执行读多写少这类操作的场景中, 相较于普通互斥锁, 哪一种锁更为适用”, 你需要给出明确的答案, 同时说明这种选型所伴随的性能开销或潜在代价。欢迎大家在这里提交你的解答内容。