【三大锁王争霸赛:Java锁、数据库锁、分布式锁谁是卷王?】

话说在编程界,"并发"这哥们就是个调皮捣蛋的熊孩子,总爱趁多个线程/进程抢资源时搞破坏------比如让两个线程同时给你转账,结果钱少了一半;或者多个请求同时改一条数据,最后数据乱成一锅粥。

为了治住这熊孩子,"锁"家族应运而生。今天咱们就请出锁家族的三位顶流:Java锁 (家里的防盗门)、数据库锁 (公司的文件柜锁)、分布式锁(小区的共享充电桩锁),聊聊它们各自的干活套路和适用场景。

一、Java锁:程序内部的"家庭防盗门"

先说说Java锁,它管的是"自家一亩三分地"------也就是单个Java进程内部的线程安全。就像你家的防盗门,只能防外人进你家,管不了邻居家的事。

举个栗子:你和你对象共用一个支付宝账号(相当于一个共享资源),俩人同时想给同一主播打赏(两个线程并发)。如果没锁,可能出现你扣了钱但主播没收到,或者俩人一共打赏100,结果扣了200的骚操作。这时候Java锁就派上用场了,它会喊一句:"排队!一个一个来!"

1. 乐观锁:佛系青年,先干了再说

乐观锁就像个佛系老板,觉得员工们都很自觉,不会抢东西。它干活的逻辑是:"你先干,干完我再检查有没有人跟你抢。" 对应到Java里,最典型的就是CAS(Compare and Swap)

比如你要给主播打赏100,乐观锁会先记着当前账户余额(假设1000),然后执行扣钱操作。扣完之后,它会检查:"哎?现在的余额是不是我刚才记的1000扣完100的900?" 如果是,就成了;如果不是(比如你对象同时扣了200),就说:"哦豁,有人抢了,你重新来一遍吧。"

适用场景:抢东西的人少(并发低),比如你和你对象很少同时打赏。优点是不用排队,效率高;缺点是抢的人多了,会不停重试,像个复读机一样,反而麻烦。

2. 悲观锁:铁腕总裁,先锁了再说

悲观锁跟乐观锁正好相反,是个铁腕总裁,觉得"人心险恶,不锁就会乱"。它干活的逻辑是:"先把资源锁起来,谁拿到钥匙谁干活,其他人都等着!" Java里的synchronized 关键字、ReentrantLock都是悲观锁的代表。

还是打赏的例子,悲观锁会先把支付宝账号锁起来,你拿到钥匙后,想怎么打赏就怎么打赏,你对象只能在旁边排队。等你干完了,把钥匙交出来,你对象才能接着干。

适用场景:抢东西的人多(并发高),比如你和你对象天天一起给不同主播打赏。优点是不会出错,稳得一批;缺点是排队太费时间,效率低------就像食堂打饭,就一个窗口,人多的时候能排到天荒地老。

二、数据库锁:公司里的"文件柜锁"

Java锁管的是单个程序内部,可如果多个程序同时操作同一个数据库呢?比如你用手机支付宝打赏,你对象用电脑支付宝打赏,这俩支付宝程序是两个不同的Java进程,Java锁就管不着了。这时候,就得请出数据库锁------相当于公司里的文件柜锁,管的是多个程序共享的数据库资源。

再举个栗子:公司的财务小姐姐要给员工发工资(更新数据库里的工资表),同时老板要查员工工资(查询工资表)。如果没锁,可能出现财务小姐姐刚改了工资,还没保存,老板就查了,看到的是旧数据;或者两个财务同时给一个员工发工资,结果发了双份。

1. 行锁:精准打击,只锁一行数据

行锁就像个精准的狙击手,只锁你要操作的那一行数据,不影响其他行。比如财务小姐姐给"张三"发工资,行锁只会把"张三"那一行锁住,老板查"李四"的工资完全不受影响。

这是最常用的锁,优点是粒度细,不耽误别人干活,效率高。但要注意,只有用了索引(比如按员工ID查),数据库才会用行锁;如果没索引,它会把整个表都锁住,那就麻烦了。

2. 表锁:地毯式轰炸,锁整个表

表锁就像地毯式轰炸,不管你要操作哪一行,直接把整个表都锁住。比如财务小姐姐要批量修改所有员工的工资,表锁会把工资表整个锁起来,这时候谁都不能查、不能改工资,只能等着。

适用场景:很少用,一般是全表更新的时候才用。优点是简单粗暴,不容易出错;缺点是太霸道,其他人都得等着,效率极低------就像公司大扫除,把整个办公区都封了,所有人都得停工。

3. 读写锁:劳逸结合,读和写分开锁

读写锁是个聪明的锁,它知道"读数据不会乱,写数据才会乱",所以把锁分成了两种:读锁(共享锁)和写锁(排他锁)。

规则很简单:多个读锁可以同时存在(比如多个老板同时查工资,互不影响);但写锁和读锁、写锁和写锁不能同时存在(比如财务小姐姐写工资的时候,老板不能查;两个财务小姐姐不能同时写)。

这就像公司的文件柜,多人可以同时看文件,但有人要改文件的时候,其他人既不能看也不能改。优点是兼顾了读的效率和写的安全性,比单纯的行锁、表锁灵活多了。

三、分布式锁:分布式系统的"共享充电桩锁"

现在问题又来了:如果你的应用是分布式的,比如支付宝有100台服务器(100个节点),你用手机连的是服务器A,你对象用电脑连的是服务器B,这时候Java锁和数据库锁都管不着了------Java锁只能管单个服务器内部,数据库锁虽然能管,但频繁用会拖慢数据库,而且容易出死锁。

这时候,分布式锁就该登场了。它相当于小区里的共享充电桩,不管你是哪栋楼的业主(哪个服务器节点),要用电都得先抢这个锁,谁抢到谁用。分布式锁的核心逻辑是:让所有分布式节点都去抢同一个"唯一资源",抢到的就获得锁,没抢到的就等着

1. 基于Redis的分布式锁:最热门的"抢锁神器"

Redis是个高性能的缓存数据库,用它做分布式锁就像用手机抢演唱会门票一样,快、准、狠。核心思路是:用Redis的一个key作为锁,谁能成功设置这个key,谁就获得锁;用完之后,删除这个key,释放锁。

举个栗子:你和你对象同时抢着给主播打赏,两台服务器同时向Redis发起请求:"我要设置key=alipay_lock,value=my_server"。Redis会告诉其中一台服务器:"设置成功,锁是你的了!" 另一台服务器则会收到:"失败,key已经存在了,你等着吧。"

但这里有个坑:如果拿到锁的服务器突然挂了,没来得及删除key,那这个锁就永远没人释放了,其他人都抢不到。所以得给key加个过期时间,比如10秒,就算服务器挂了,10秒后key自动消失,锁就释放了。

优点:性能好,响应快,支持高并发;缺点:需要自己处理过期时间、重入(同一个节点多次抢锁)等问题,容易踩坑。

2. 基于ZooKeeper的分布式锁:最稳的"锁王"

ZooKeeper是个分布式协调工具,用它做分布式锁就像小区里的物业,靠谱、稳当。核心思路是:在ZooKeeper里创建一个临时有序节点,节点序号最小的那个节点,就获得锁;用完之后,删除节点,释放锁。如果节点对应的服务器挂了,ZooKeeper会自动删除这个临时节点,锁也就自动释放了。

还是抢打赏的例子,两台服务器在ZooKeeper里创建节点,分别是"lock_001"和"lock_002"。序号最小的"lock_001"获得锁,"lock_002"则会盯着"lock_001",等它消失了,自己就变成最小的,获得锁。

优点:自带重入、过期释放机制,不用自己处理,稳得一批;缺点:性能比Redis差一点,部署和维护ZooKeeper也比较麻烦------就像物业虽然靠谱,但要交物业费,还得配合它的管理。

四、三大锁王对比:什么时候该选谁?

说了这么多,可能有人还是分不清该用哪个锁。其实很简单,记住三句话:

  1. 单个程序内部并发:用Java锁。比如你写的一个单机程序,多个线程抢资源,选乐观锁还是悲观锁,看并发量高低。

  2. 多个程序共享数据库:用数据库锁。比如两个不同的程序同时操作同一个数据库表,用行锁、读写锁就够了,别轻易用表锁。

  3. 分布式系统(多个节点)并发:用分布式锁。比如你的应用部署在多台服务器上,选Redis锁(追求性能)或ZooKeeper锁(追求稳定)。

最后总结一下:三大锁王没有好坏之分,只有适用场景不同。就像防盗门、文件柜锁、共享充电桩锁,各自管各自的领域,缺一不可。理解了它们的"干活套路",再遇到并发问题,就能精准选对锁,治住"并发"这个熊孩子啦!

相关推荐
Jelly-小丑鱼2 小时前
Linux搭建SQLserver数据库和Orical数据库
linux·运维·数据库·sqlserver·oracal·docker容器数据库
JIngJaneIL2 小时前
基于springboot + vue健康管理系统(源码+数据库+文档)
java·开发语言·数据库·vue.js·spring boot·后端
电商API&Tina2 小时前
【电商API接口】关于电商数据采集相关行业
java·python·oracle·django·sqlite·json·php
刘一说2 小时前
Spring Boot中IoC(控制反转)深度解析:从实现机制到项目实战
java·spring boot·后端
悟空码字2 小时前
SpringBoot参数配置:一场“我说了算”的奇幻之旅
java·spring boot·后端
zhcf2 小时前
【MySQL】聚簇索引与非聚簇索引
数据库·mysql
我居然是兔子2 小时前
Java虚拟机(JVM)内存模型与垃圾回收全解析
java·开发语言·jvm
关于不上作者榜就原神启动那件事2 小时前
Spring Data Redis 中的 opsFor 方法详解
java·redis·spring
其美杰布-富贵-李2 小时前
Java (Spring Boot) 反射完整学习笔记
java·spring boot·学习