终极锁实战单JVM锁 分布式锁.docx
- 文档编号:7384088
- 上传时间:2023-01-23
- 格式:DOCX
- 页数:30
- 大小:28.07KB
终极锁实战单JVM锁 分布式锁.docx
《终极锁实战单JVM锁 分布式锁.docx》由会员分享,可在线阅读,更多相关《终极锁实战单JVM锁 分布式锁.docx(30页珍藏版)》请在冰豆网上搜索。
终极锁实战单JVM锁分布式锁
终极锁实战:
单JVM锁+分布式锁
1.前言
2.单JVM锁
3.分布式锁
4.总结
=========正文分割线=================
1.前言
锁就像一把钥匙,需要加锁的代码就像一个房间。
出现互斥操作的典型场景:
多人同时想进同一个房间争抢这个房间的钥匙(只有一把),一人抢到钥匙,其他人都等待这个人出来归还钥匙,此时大家再次争抢钥匙循环下去。
作为终极实战系列,本篇用java语言分析锁的原理(源码剖析)和应用(详细代码),根据锁的作用范围分为:
JVM锁和分布式锁。
如理解有误之处,还请指出。
2.单JVM锁(进程级别)
程序部署在一台服务器上,当容器启动时(例如tomcat),一台JVM就运行起来了。
本节分析的锁均只能在单JVM下生效。
因为最终锁定的是某个对象,这个对象生存在JVM中,自然锁只能锁单JVM。
这一点很重要。
如果你的服务只部署一个实例,那么恭喜你,用以下几种锁就可以了。
1.synchronized同步锁
2.ReentrantLock重入锁
3.ReadWriteLock读写锁
4.StampedLock戳锁
由于之前已经详细分析过原理+使用,各位直接坐飞机吧:
同步中的四种锁synchronized、ReentrantLock、ReadWriteLock、StampedLock
3.分布式锁(多服务节点,多进程)
3.1基于数据库锁实现
场景举例:
卖商品,先查询库存>0,更新库存-1。
1.悲观锁:
selectforupdate(一致性锁定读)
查询官方文档如上图,事务内起作用的行锁。
能够保证当前session事务所锁定的行不会被其他session所修改(这里的修改指更新或者删除)。
对读取的记录加X锁,即排它锁,其他事不能对上锁的行加任何锁。
BEGIN;(确保以下2步骤在一个事务中:
)
SELECT*FROMtb_product_stockWHEREproduct_id=1FORUPDATE--->product_id有索引,锁行.加锁(注:
条件字段必须有索引才能锁行,否则锁表,且最好用explain查看一下是否使用了索引,因为有一些会被优化掉最终没有使用索引)
UPDATEtb_product_stockSETnumber=number-1WHEREproduct_id=1--->更新库存-1.解锁
COMMIT;
2.乐观锁:
版本控制,选一个字段作为版本控制字段,更新前查询一次,更新时该字段作为更新条件。
不同业务场景,版本控制字段,可以01控制,也可以+1控制,也可以-1控制,这个随意。
BEGIN;(确保以下2步骤在一个事务中:
)
SELECTnumberFROMtb_product_stockWHEREproduct_id=1--》查询库存总数,不加锁
UPDATEtb_product_stockSETnumber=number-1WHEREproduct_id=1ANDnumber=第一步查询到的库存数--》number字段作为版本控制字段
COMMIT;
3.2基于缓存实现(redis,memcached)
原理:
redisson开源jar包,提供了很多功能,其中就包含分布式锁。
是Redis官方推荐的顶级项目,官网飞机票
核心org.redisson.api.RLock接口封装了分布式锁的获取和释放。
源码如下:
1@Override
2publicbooleantryLock(longwaitTime,longleaseTime,TimeUnitunit)throwsInterruptedException{
3longtime=unit.toMillis(waitTime);
4longcurrent=System.currentTimeMillis();
5finallongthreadId=Thread.currentThread().getId();
6Longttl=tryAcquire(leaseTime,unit,threadId);//申请锁,返回还剩余的锁过期时间
7//lockacquired
8if(ttl==null){
9returntrue;
10}
11
12time-=(System.currentTimeMillis()-current);
13if(time<=0){
14acquireFailed(threadId);
15returnfalse;
16}
17
18current=System.currentTimeMillis();
19finalRFuture<RedissonLockEntry>subscribeFuture=subscribe(threadId);
20if(!
await(subscribeFuture,time,TimeUnit.MILLISECONDS)){
21if(!
subscribeFuture.cancel(false)){
22subscribeFuture.addListener(newFutureListener<RedissonLockEntry>(){
23@Override
24publicvoidoperationComplete(Future<RedissonLockEntry>future)throwsException{
25if(subscribeFuture.isSuccess()){
26unsubscribe(subscribeFuture,threadId);
27}
28}
29});
30}
31acquireFailed(threadId);
32returnfalse;
33}
34
35try{
36time-=(System.currentTimeMillis()-current);
37if(time<=0){
38acquireFailed(threadId);
39returnfalse;
40}
41
42while(true){
43longcurrentTime=System.currentTimeMillis();
44ttl=tryAcquire(leaseTime,unit,threadId);
45//lockacquired
46if(ttl==null){
47returntrue;
48}
49
50time-=(System.currentTimeMillis()-currentTime);
51if(time<=0){
52acquireFailed(threadId);
53returnfalse;
54}
55
56//waitingformessage
57currentTime=System.currentTimeMillis();
58if(ttl>=0&&ttl<time){
59getEntry(threadId).getLatch().tryAcquire(ttl,TimeUnit.MILLISECONDS);
60}else{
61getEntry(threadId).getLatch().tryAcquire(time,TimeUnit.MILLISECONDS);
62}
63
64time-=(System.currentTimeMillis()-currentTime);
65if(time<=0){
66acquireFailed(threadId);
67returnfalse;
68}
69}
70}finally{
71unsubscribe(subscribeFuture,threadId);
72}
73//returnget(tryLockAsync(waitTime,leaseTime,unit));
74}
上述方法,调用加锁的逻辑就是在tryAcquire(leaseTime,unit,threadId)中,如下图:
1privateLongtryAcquire(longleaseTime,TimeUnitunit,longthreadId){
2returnget(tryAcquireAsync(leaseTime,unit,threadId));//tryAcquireAsync返回RFutrue
3}
tryAcquireAsync中commandExecutor.evalWriteAsync就是咱们加锁核心方法了
1<T>RFuture<T>tryLockInnerAsync(longleaseTime,TimeUnitunit,longthreadId,RedisStrictCommand<T>command){
2internalLockLeaseTime=unit.toMillis(leaseTime);
3
4returncommandExecutor.evalWriteAsync(getName(),LongCodec.INSTANCE,command,
5"if(redis.call('exists',KEYS[1])==0)then"+
6"redis.call('hset',KEYS[1],ARGV[2],1);"+
7"redis.call('pexpire',KEYS[1],ARGV[1]);"+
8"returnnil;"+
9"end;"+
10"if(redis.call('hexists',KEYS[1],ARGV[2])==1)then"+
11"redis.call('hincrby',KEYS[1],ARGV[2],1);"+
12"redis.call('pexpire',KEYS[1],ARGV[1]);"+
13"returnnil;"+
14"end;"+
15"returnredis.call('pttl',KEYS[1]);",
16Collections.<Object>singletonList(getName()),internalLockLeaseTime,getLockName(threadId));
17}
如上图,已经到了redis命令了
加锁:
KEYS[1]:
需要加锁的key,这里需要是字符串类型。
ARGV[1]:
锁的超时时间,防止死锁
ARGV[2]:
锁的唯一标识,(UUID.randomUUID())+“:
”+threadId
1//检查是否key已经被占用,如果没有则设置超时时间和唯一标识,初始化value=1
2if(redis.call('exists',KEYS[1])==0)
3then
4redis.call('hset',KEYS[1],ARGV[2],1);//hsetkeyfieldvalue哈希数据结构
5redis.call('pexpire',KEYS[1],ARGV[1]);//pexpirekeyexpireTime设置有效时间
6returnnil;
7end;
8//如果锁重入,需要判断锁的keyfield都一直情况下value加一
9if(redis.call('hexists',KEYS[1],ARGV[2])==1)
10then
11redis.call('hincrby',KEYS[1],ARGV[2],1);//hincrbykeyfiledaddValue加1
12redis.call('pexpire',KEYS[1],ARGV[1]);//pexpirekeyexpireTime重新设置超时时间
13returnnil;
14end;
15//返回剩余的过期时间
16returnredis.call('pttl',KEYS[1]);
以上的方法,当返回空是,说明获取到锁,如果返回一个long数值(pttl命令的返回值),说明锁已被占用,通过返回剩余时间,外部可以做一些等待时间的判断和调整。
不再分析解锁步骤,直接贴上解锁的redis命令
解锁:
–KEYS[1]:
需要加锁的key,这里需要是字符串类型。
–KEYS[2]:
redis消息的ChannelName,一个分布式锁对应唯一的一个channelName:
“redisson_lock__channel__{”+getName()+“}”
–ARGV[1]:
reids消息体,这里只需要一个字节的标记就可以,主要标记redis的key已经解锁,再结合redis的Subscribe,能唤醒其他订阅解锁消息的客户端线程申请锁。
–ARGV[2]:
锁的超时时间,防止死锁
–ARGV[3]:
锁的唯一标识,(UUID.randomUUID())+“:
”+threadId
1//如果key已经不存在,说明已经被解锁,直接发布(publihs)redis消息
2if(redis.call('exists',KEYS[1])==0)
3then
4redis.call('publish',KEYS[2],ARGV[1]);//publishChannelNamemessage向信道发送解锁消息
5return1;
6end;
7//key和field不匹配,说明当前客户端线程没有持有锁,不能主动解锁。
8if(redis.call('hexists',KEYS[1],ARGV[3])==0)
9then
10returnnil;
11end;
12//将value减1
13localcounter=redis.call('hincrby',KEYS[1],ARGV[3],-1);//hincrbykeyfiledaddValue减1
14//如果counter>0说明锁在重入,不能删除key
15if(counter>0)
16then
17redis.call('pexpire',KEYS[1],ARGV[2]);
18return0;
19else
20//删除key并且publish解锁消息
21redis.call('del',KEYS[1]);
22redis.call('publish',KEYS[2],ARGV[1]);
23return1;
24end;
25returnnil;
特点:
逻辑并不复杂,实现了可重入功能,通过pub/sub功能来减少空转,性能极高。
实现了Lock的大部分功能,支持强制解锁。
实战:
1.创建客户端配置类:
这里我们最终只用了一种来测试,就是initSingleServerConfig单例模式。
1packagedistributed.lock.redis;
2
3importorg.redisson.config.Config;
4
5/**
6*
7*@ClassName:
RedissionConfig
8*@Description:
自定义RedissionConfig初始化方法
9*支持自定义构造:
单例模式,集群模式,主从模式,哨兵模式。
10*注:
此处使用springbean配置文件保证bean单例,见applicationContext-redis.xml
11*大家也可以用工厂模式自己维护单例:
本类生成RedissionConfig,再RedissonClientredisson=Redisson.create(config);这样就可以创建RedissonClient
12*@authordiandian.zhang
13*@date2017年7月20日下午12:
55:
50
14*/
15publicclassRedissionConfig{
16privateRedissionConfig(){
17}
18
19publicstaticConfiginitSingleServerConfig(StringredisHost,StringredisPort,StringredisPassword){
20returninitSingleServerConfig(redisHost,redisPort,redisPassword,0);
21}
22
23/**
24*
25*@Description使用单例模式初始化构造Config
26*@paramredisHost
27*@paramredisPort
28*@paramredisPassword
29*@paramredisDatabaseredisdb默认0(0~15)有redis.conf配置文件中参数来控制数据库总数:
database16.
30*@return
31*@authordiandian.zhang
32*@date2017年7月20日下午12:
56:
21
33*@sinceJDK1.8
34*/
35publicstaticConfiginitSingleServerConfig(StringredisHost,StringredisPort,StringredisPassword,IntegerredisDatabase){
36Configconfig=newConfig();
37config.useSingleServer().setAddress(redisHost+":
"+redisPort)
38.setPassword(redisPassword)
39.setDatabase(redisDatabase);//可以不设置,看业务是否需要隔离
40//RedissonClientredisson=Redisson.create(config);
41returnconfig;
42}
43
44/**
45*
46*@Description集群模式
47*@parammasterAddress
48*@paramnodeAddressArray
49*@return
50*@authordiandian.zhang
51*@date2017年7月20日下午3:
29:
32
52*@sinceJDK1.8
53*/
54publicstaticConfiginitClusterServerConfig(StringmasterAddress,String[]nodeAddressArray){
55StringnodeStr="";
56for(Stringslave:
nodeAddressArray){
57nodeStr+=","+slave;
58}
59Configconfig=newConfig();
60config.useClusterServers()
61.setScanInterval(2000)//clusterstatescanintervalinmilliseconds
62.addNodeAddress(nodeStr);
63returnconfig;
64}
65
66/**
67*
68*@Description主从模式
69*@parammasterAddress一主
70*@paramslaveAddressArray多从
71*@return
72*@authordiandian.zhang
73*@date2017年7月20日下午2:
29:
38
74*@sinceJDK1.8
75*/
76publicstaticConfiginitMasterSlaveServerConfig(StringmasterAddress,String[]slaveAddressArray){
77StringslaveStr="";
78for(Stringslave:
slaveAddressArray){
79slaveStr+=","+slave;
80}
81Configconfig=newConfig();
82config.useMasterSlaveServers()
83.setMasterAddress(masterAddress)//一主
84.addSlaveAddress(slaveStr);//多从"127.0.0.1:
26389","127.0.0.1:
26379"
85returnconfig;
86}
87
88/**
89*
90*@Description哨兵模式
91*@parammasterAddress
92*@paramslaveAddressArray
93*@return
94*@authordiandian.zhang
95*@date2017年7月20日下午3:
01:
35
96*@sinceJDK1.8
97*/
98publicstaticConfiginitSentinelServerConfig(StringmasterAddress,String[]sentinelAddressArray){
99StringsentinelStr="";
100for(Stringsentinel:
sentinelAddressArray){
101sentinelStr+=","+sentinel;
10
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 终极锁实战单JVM锁 分布式锁 终极 实战 JVM 分布式
![提示](https://static.bdocx.com/images/bang_tan.gif)