




版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
分布式锁可以这么简单?本⽂只讨论如何基于已实现了分布式锁的第三⽅框架进⾏⼆次封装,减少分布式锁的使⽤成本,⽽且当需要替换分布式锁实现时,只需要少量代码的调整,⽐如只需修改配置⽂件即可完成改造。另外,本⽂是对另⼀篇⽂章中的实现的优化版,但主要思想是⼀致的。见使⽤Redisson实现分布式锁,SpringAOP简化之。源码在开始之前,我们先回想⼀个⽐较经典的场景——超卖,⽽解决超卖的⼀个⽅案就是加锁,真正扣减库存之前,必须拿到对应的锁,下⾯来看⼀段⽰例代码,其中锁的实现是借助了Redisson:@Transactional(rollbackFor=Throwable.class)publicvoidseckill(LongitemId,intpurchaseCount){RLocklock=redissonClient.getLock("item:"+itemId);booleanlocked=false;try{locked=lock.tryLock(5000,TimeUnit.MILLISECONDS);if(locked){doSeckill(itemId,purchaseCount);}}catch(InterruptedExceptione){thrownewRuntimeException("⽆法在指定时间内获得锁");}finally{//是否未获得锁if(!locked){thrownewRuntimeException("尝试获取锁超时,锁获取失败");}if(lock.isHeldByCurrentThread()){lock.unlock();}else{thrownewRuntimeException("锁释放失败,当前线程不是锁的持有者");}}}publicvoiddoSeckill(LongitemId,intpurchaseCount){//获取库存//⽐较并扣减库存//更新库存//异步执⾏其他逻辑}上⾯的代码,看着也算还好,不太复杂,但其实除了真正的业务逻辑doSeckill外,其他的都是⼀些可模板化的代码结构。想象⼀下如果每⼀处使⽤分布式锁的地⽅都要写这堆东西,那还得了,更可怕的是,如果要换⼀种分布式锁的实现⽅式呢?既然我们能预想到这种写法有这么严重的弊端,那就得想办法优化。最好能够实现:只需要关注真正的业务逻辑,需要使⽤分布式锁时,只需增加少量代码即可,⽐如加个注解;另外,如果需要更换分布式锁的实现⽅式,也不需要改任何代码。怎么实现呢?这就是这篇⽂章要解决的问题了。通过优化后,上⾯的⽰例代码将可以变得如下这么简单:@DistributedLock(lockName="#{#itemId}",lockNamePre="item")publicvoiddoSeckill(LongitemId,intpurchaseCount){//获取库存//⽐较并扣减库存//更新库存//异步执⾏其他逻辑}下⾯,正式开始~~~Redisson概述可参考另⼀篇⽂章的Redisson概述。使⽤Redisson实现分布式锁1.定义回调接⼝/***分布锁回调接⼝*/publicinterfaceDistributedLockCallback<T>{/***调⽤者必须在此⽅法中实现需要加分布式锁的业务逻辑**@return*/publicTprocess()throwsThrowable;/***得到分布式锁名称**@return*/publicStringgetLockName();}定义分布式具体实现的锁模板接⼝/***分布式锁具体实现的模板接⼝**@authorsprainkle*@date2019.04.20*/publicinterfaceDistributedLockTemplate{/**尝试获取锁的默认等待时间*/longDEFAULT_WAIT_TIME=DistributedLock.DEFAULT_WAIT_TIME;/**锁的默认超时时间.超时后,锁会被⾃动释放*/longDEFAULT_TIMEOUT=DistributedLock.DEFAULT_WAIT_TIME;/**时间单位。默认为毫秒。*/TimeUnitDEFAULT_TIME_UNIT=DistributedLock.DEFAULT_TIME_UNIT;/**获得锁名时拼接前后缀⽤到的分隔符*/StringDEFAULT_SEPARATOR=DistributedLock.DEFAULT_SEPARATOR;/**lockName后缀*/StringLOCK=DistributedLock.LOCK;/***使⽤分布式锁,使⽤锁默认超时时间。***@paramcallback*@paramfairLock是否使⽤公平锁*@return*/<T>Tlock(DistributedLockCallback<T>callback,booleanfairLock);/***使⽤分布式锁。⾃定义锁的超时时间**@paramcallback*@paramleaseTime锁超时时间。超时后⾃动释放锁。*@paramtimeUnit*@paramfairLock是否使⽤公平锁*@return*/<T>Tlock(DistributedLockCallback<T>callback,longleaseTime,TimeUnittimeUnit,booleanfairLock);/***尝试分布式锁,使⽤锁默认等待时间、超时时间。**@paramcallback*@param<T>*@paramfairLock是否使⽤公平锁*@return*/<T>TtryLock(DistributedLockCallback<T>callback,booleanfairLock);/***尝试分布式锁,⾃定义等待时间、超时时间。**@paramcallback*@paramwaitTime获取锁最长等待时间*@paramleaseTime锁超时时间。超时后⾃动释放锁。*@paramtimeUnit*@param<T>*@paramfairLock是否使⽤公平锁*@return*/<T>TtryLock(DistributedLockCallback<T>callback,longwaitTime,longleaseTime,TimeUnittimeUnit,booleanfairLock);/***锁是否由当前线程持有**@paramlock*@return*/booleanisHeldByCurrentThread(Objectlock);}基于Redisson实现/***BaseDistributedLockTemplate.⽤于封装⼀些公共⽅法*/publicabstractclassAbstractDistributedLockTemplateimplementsDistributedLockTemplate{/***处理业务逻辑**@paramcallback*@param<T>*@return业务逻辑处理结果*/protected<T>Tprocess(DistributedLockCallback<T>callback){try{returncess();}catch(Throwablee){if(einstanceofBaseException){throw(BaseException)e;}thrownewBaseException(CommonResponseEnum.SERVER_ERROR,null,e.getMessage(),e);}}}@Slf4jpublicclassRedisDistributedLockTemplateextendsAbstractDistributedLockTemplate{privatefinalRedissonClientredisson;privatefinalStringnamespace;privatefinallonglockTimeoutMs;privatefinallongwaitTimeoutMs;//锁前缀privatefinalStringlockPrefix;//锁后缀privatefinalStringlockPostfix;publicRedisDistributedLockTemplate(RedissonClientredisson,DistributedLockPropertiesproperties){this.redisson=redisson;space=properties.getNamespace();this.lockTimeoutMs=Optional.ofNullable(properties.getLockTimeoutMs()).orElse(DEFAULT_TIMEOUT);this.waitTimeoutMs=Optional.ofNullable(properties.getWaitTimeoutMs()).orElse(DEFAULT_WAIT_TIME);this.lockPrefix=namespace+DEFAULT_SEPARATOR;this.lockPostfix=".lock";}@Overridepublic<T>Tlock(DistributedLockCallback<T>callback,booleanfairLock){returnlock(callback,lockTimeoutMs,DEFAULT_TIME_UNIT,fairLock);}@Overridepublic<T>Tlock(DistributedLockCallback<T>callback,longleaseTime,TimeUnittimeUnit,booleanfairLock){RLocklock=getLock(callback.getLockName(),fairLock);try{lock.lock(leaseTime,timeUnit);returnprocess(callback);}finally{if(lock!=null&&lock.isHeldByCurrentThread()){lock.unlock();}}}@Overridepublic<T>TtryLock(DistributedLockCallback<T>callback,booleanfairLock){returntryLock(callback,waitTimeoutMs,lockTimeoutMs,DEFAULT_TIME_UNIT,fairLock);returntryLock(callback,waitTimeoutMs,lockTimeoutMs,DEFAULT_TIME_UNIT,fairLock);}@Overridepublic<T>TtryLock(DistributedLockCallback<T>callback,longwaitTime,longleaseTime,TimeUnittimeUnit,booleanfairLock){RLocklock=getLock(callback.getLockName(),fairLock);booleanlocked=false;DistributedLockContextcontext=DistributedLockContextHolder.getContext();context.setLock(lock);try{locked=lock.tryLock(waitTime,leaseTime,timeUnit);if(locked){returnprocess(callback);}}catch(InterruptedExceptione){ResponseEnum.LOCK_NOT_YET_HOLD.assertFailWithMsg("⽆法在指定时间内获得锁",e);}finally{//是否未获得锁if(!locked){ResponseEnum.LOCK_NOT_YET_HOLD.assertFailWithMsg("尝试获取锁超时,获取失败.");}if(lock.isHeldByCurrentThread()){lock.unlock();}else{log.warn("锁释放失败,当前线程不是锁的持有者");}}returnnull;}publicRLockgetLock(StringlockName,booleanfairLock){RLocklock;lockName=lockPrefix+lockName+lockPostfix;if(fairLock){lock=redisson.getFairLock(lockName);}else{lock=redisson.getLock(lockName);}returnlock;}@OverridepublicbooleanisHeldByCurrentThread(Objectlock){if(!(lockinstanceofRLock)){returnfalse;}return((RLock)lock).isHeldByCurrentThread();}}简单使⽤RedisDistributedLockTemplateDistributedLockTemplatelockTemplate=...;finalStringlockName=...;lockTemplate.lock(newDistributedLockCallback<Object>(){@OverridepublicObjectprocess(){//dosomebusinessreturnnull;}@OverridepublicStringgetLockName(){returnlockName;}},false);会不会还是很⿇烦?虽说⽐使⽤原⽣Redisson时简单⼀点点,但是每次使⽤分布式锁都要写类似上⾯的重复代码,还是不够优雅。有没有什么⽅法可以只关注核⼼业务逻辑代码的编写,即上⾯的"dosomebusiness"。下⾯介绍如何使⽤SpringAOP来实现这⼀⽬标。使⽤SpringAOP进⼀步封装定义注解DistributedLock/***<pre>*可以使⽤该注解实现分布式锁。**获取lockName的优先级为:lockName>argNum>param**使⽤的是公平锁,即先来先得.*</pre>*/@Target({ElementType.METHOD})@Retention(RetentionPolicy.RUNTIME)@Documentedpublic@interfaceDistributedLock{/***锁的名称。*如果lockName可以确定,直接设置该属性。*<br><br>*⽀持SpEL,格式为:#{expression},内置#root,属性包括:target,method,args等,其中target为注解所在类的SpringBean*也⽀持占位符${}*/StringlockName()default"";/***lockName前缀*/StringlockNamePre()default"";/***lockName后缀*@see#LOCK*/StringlockNamePost()default"";/***在开始加锁前,执⾏某个⽅法进⾏校验.*<br><br>*⽀持SpEL,格式为:#{expression},所以⽅法必须为public,如果⽅法的所在SpringBean与注解的⽅法相同,*写法为:#{#root.target.yourMethod(#param1,#param2,...)}**@return*/StringcheckBefore()default"";StringcheckBefore()default"";/***获得锁名时拼接前后缀⽤到的分隔符*@see#DEFAULT_SEPARATOR*/Stringseparator()defaultDEFAULT_SEPARATOR;/***<pre>*获取注解的⽅法参数列表的某个参数对象的某个属性值来作为lockName。因为有时候lockName是不固定的。*当param不为空时,可以通过argSeq参数来设置具体是参数列表的第⼏个参数,不设置则默认取第⼀个。*</pre>*/Stringparam()default"";/***将⽅法第argSeq个参数作为锁名.0为⽆效值.*/intargSeq()default0;/***是否使⽤公平锁。*公平锁即先来先得。*/booleanfairLock()defaultfalse;/***是否使⽤尝试锁。*/booleantryLock()defaulttrue;/***最长等待时间。*该字段只有当tryLock()返回true才有效。*@see#DEFAULT_WAIT_TIME*/longwaitTime()defaultDEFAULT_WAIT_TIME;/***<pre>*锁超时时间。*超时时间过后,锁⾃动释放。*建议:*尽量缩简需要加锁的逻辑。*</pre>*@see#DEFAULT_TIMEOUT*/longleaseTime()defaultDEFAULT_TIMEOUT;/***时间单位。默认为毫秒。*/TimeUnittimeUnit()defaultTimeUnit.MILLISECONDS;/***尝试获取锁的默认等待时间*/longDEFAULT_WAIT_TIME=10000L;/***锁的默认超时时间.超时后,锁会被⾃动释放*/longDEFAULT_TIMEOUT=5000L;/***时间单位。默认为毫秒。*/TimeUnitDEFAULT_TIME_UNIT=TimeUnit.MILLISECONDS;/***获得锁名时拼接前后缀⽤到的分隔符*/StringDEFAULT_SEPARATOR=":";/***lock*/*/StringLOCK="lock";}定义切⾯织⼊的代码/***分布式锁切⾯逻辑*/@Slf4j@AspectpublicclassDistributedLockAspectimplementsApplicationContextAware,BeanFactoryAware,Ordered{/***解析模板*/privatestaticfinalParserContextPARSER_CONTEXT=ParserContext.TEMPLATE_EXPRESSION;/***SpEL解析器*/privatestaticfinalSpelExpressionParserspElParser=newSpelExpressionParser();privateApplicationContextapplicationContext;privateBeanFactorybeanFactory;/***⽤于解析@BeanName为对应的SpringBean*/privateBeanResolverbeanResolver;privatefinalDistributedLockTemplatelockTemplate;publicDistributedLockAspect(DistributedLockTemplatelockTemplate){this.lockTemplate=lockTemplate;}@Around(value="@annotation(distributedLock)")publicObjectdoAround(ProceedingJoinPointpjp,DistributedLockdistributedLock){EvaluationContextevaluationCtx=getEvaluationContext(pjp);doCheckBefore(distributedLock,evaluationCtx);StringlockName=getLockName(pjp,evaluationCtx);returnlock(pjp,lockName);}/***执⾏{@linkDistributedLock#checkBefore()}指定的⽅法**@paramdistributedLock*@paramevaluationCtx*/privatevoiddoCheckBefore(DistributedLockdistributedLock,EvaluationContextevaluationCtx){StringcheckBefore=distributedLock.checkBefore();resolveExpression(evaluationCtx,checkBefore);}/***获取锁名**@paramjp*@return*/privateStringgetLockName(JoinPointjp,EvaluationContextevaluationCtx){DistributedLockannotation=getAnnotation(jp);StringlockName=annotation.lockName();if(StrUtil.isNotBlank(lockName)){lockName=resolveExpression(evaluationCtx,lockName);}else{Object[]args=jp.getArgs();if(args.length>0){Stringparam=annotation.param();if(StrUtil.isNotBlank(param)){Objectarg;if(annotation.argSeq()>0){arg=args[annotation.argSeq()-1];}else{arg=args[0];}lockName=String.valueOf(getParam(arg,param));}elseif(annotation.argSeq()>0){lockName=String.valueOf(args[annotation.argSeq()-1]);}}}if(StrUtil.isBlank(lockName)){CommonResponseEnum.SERVER_ERROR.assertFailWithMsg("⽆法⽣成分布式锁锁名.annotation:{0}",annotation);}StringpreLockName=annotation.lockNamePre();StringpostLockName=annotation.lockNamePost();Stringseparator=annotation.separator();if(StrUtil.isNotBlank(preLockName)){lockName=preLockName+separator+lockName;}if(StrUtil.isNotBlank(postLockName)){lockName=lockName+separator+postLockName;}returnlockName;}/***从⽅法参数获取数据**@paramparam*@paramarg⽅法的参数数组*@return*/privateObjectgetParam(Objectarg,Stringparam){if(StrUtil.isNotBlank(param)&&arg!=null){try{returnBeanUtil.getFieldValue(arg,param);}catch(Exceptione){CommonResponseEnum.SERVER_ERROR.assertFailWithMsg("[{0}]没有属性[{1}]",arg.getClass(),param);}}return"";}/***获取锁并执⾏**@parampjp*@paramlockName*@return*/privateObjectlock(ProceedingJoinPointpjp,finalStringlockName){DistributedLockannotation=PointCutUtils.getAnnotation(pjp,DistributedLock.class);booleanfairLock=annotation.fairLock();booleantryLock=annotation.tryLock();if(tryLock){returntryLock(pjp,annotation,lockName,fairLock);}else{returnlock(pjp,lockName,fairLock);returnlock(pjp,lockName,fairLock);}}/****@parampjp*@paramlockName*@paramfairLock*@return*/privateObjectlock(ProceedingJoinPointpjp,finalStringlockName,booleanfairLock){returnlockTemplate.lock(newDistributedLockCallback<Object>(){@OverridepublicObjectprocess()throwsThrowable{returnceed();}@OverridepublicStringgetLockName(){returnlockName;}},fairLock);}/****@parampjp*@paramannotation*@paramlockName*@paramfairLock*@return*/privateObjecttryLock(ProceedingJoinPointpjp,DistributedLockannotation,finalStringlockName,booleanfairLock){longwaitTime=annotation.waitTime(),leaseTime=annotation.leaseTime();TimeUnittimeUnit=annotation.timeUnit();returnlockTemplate.tryLock(newDistributedLockCallback<Object>(){@OverridepublicObjectprocess()throwsThrowable{returnceed();}@OverridepublicStringgetLockName(){returnlockName;}},waitTime,leaseTime,timeUnit,fairLock);}//省略若⼲}这⾥由于篇幅过长,只贴了主要代码,就不贴其他相关类的代码,如果有需要的可以去Github⾃⾏获取。如何使⽤如果是在本地测试,什么都不⽤配置,因为使⽤了springboot-starter的规范封装的,只需像其他springboot-starter⼀样,引⼊对应的依赖即可,类似如下。<dependency><groupId>com.sprainkle</groupId><artifactId>spring-cloud-advance-common-lock</artifactId><version>${spring-cloud-advance.version}</version></dependency>然后,就可以像如下代码⼀样,直接在业务代码前加上分布式锁注解即可使⽤:@DistributedLock(lockName="#{#itemId}",lockNamePre="item")publicvoiddoSeckill(LongitemId,intpurchaseCount){//获取库存//⽐较并扣减库存//更新库存//异步执⾏其他逻辑}注解DistributedLock个参数的使⽤,可参考各参数的说明。开始使⽤因为这⾥直接在模块中编写测试⽤例,所以不⽤引⼊依赖。可参考源码定义实体类TestItem@Data@TableName("test_item")publicclassTestItem{@TableIdprivateIntegerid;privateStringname;privateIntegerstock;publicTestItem(){}publicTestItem(Integerid,Stringname){this.id=id;=name;}}服务实现类TestItemService@Slf4j@ServicepublicclassTestItemServiceextendsServiceImpl<TestItemMapper,TestItem>{//具体逻辑见下⽂}本次测试ORM框架使⽤了MybatisPlus,其他类,如TestItemMapper等,由于篇幅过长,这⾥就不展⽰了。测试⽤例定义Worker类publicstaticclassWorkerimplementsRunnable{privatefinalCountDownLatchstartSignal;privatefinalCountDownLatchdoneSignal;privatefinalActionaction;publicWorker(CountDownLatchstartSignal,CountDownLatchdoneSignal,Actionaction){this.startSignal=startSignal;this.doneSignal=doneSignal;this.action=action;}@Overridepublicvoidrun(){try{System.out.println(Thread.currentThread().getName()+"start");//阻塞,直到接收到启动信号.保证所有线程的起跑线是⼀样的,即都是同时启动startSignal.await();//具体逻辑action.execute();//发送已完成信号doneSignal.countDown();}catch(Exceptione){e.printStackTrace();}}}该类是下⽂所有测试⽤例会⽤到的类。testPlainLockName⾸先我们定义TestItemService类,其中initStock为初始化库存,之后每个测试⽤例的第⼀步逻辑都是调⽤该⽅法,初始化/重置库存。⽽testPlainLockName才是该测试⽤例的主要逻辑,逻辑也很简单,就不废话了,重点看分布式锁注解。可以看到要开启分布式锁,只需在⽅法签名加上@DistributedLock(lockName="item:1",lockNamePre="item")即可,⾮常⽅便。那这么写是什么意思呢?其实就是,有很多线程在扣减商品(id=1)的库存前,需要拿到⼀把锁,锁名为distributed-lock-test:item:1.lock,其中distributed-lock-test:、.lock是框架⾃⼰补进去的,剩下的item:1则是根据lockName和lockNamePre拼接的,拼接符默认为:。很明显,lockName写死为"1"肯定不合适,这⾥只是演⽰需要,具体优化请继续往下,下⽂会给出。@Slf4j@ServicepublicclassTestItemServiceextendsServiceImpl<TestItemMapper,TestItem>{privatestaticfinalAtomicIntegeri=newAtomicInteger(10);@Transactional(rollbackFor=Throwable.class)publicTestIteminitStock(Longid,Integerstock){TestItemitem=this.getById(id);if(item==null){item=newTestItem(1,"⽜奶");}item.setStock(stock);this.saveOrUpdate(item);returnthis.getById(id);}/***锁名为固定的字符串*/@DistributedLock(lockName="1",lockNamePre="item")publicIntegertestPlainLockName(TestItemtestItem){TestItemitem=this.getById(testItem.getId());Integerstock=item.getStock();if(stock>0){stock=stock-1;item.setStock(stock);this.saveOrUpdate(item);}else{stock=-1;}returnstock;}}接下来,定义测试⽤例类。@Slf4j@RunWith(SpringRunner.class)@SpringBootTest(classes=DistributedLockTestApplication.class)publicclassDistributedLockTests{//10个线程⼀起跑privatestaticintcount=10;@AutowiredprivateTestItemServicetestItemService;@TestpublicvoidtestPlainLockName(){Consumer<TestItem>consumer=testItem->{Integerstock=testItemService.testPlainLockName(testItem);if(stock>=0){System.out.println(Thread.currentThread().getName()+":reststock="+stock);}else{System.out.println(Thread.currentThread().getName()+":soldout.");}};commonTest(consumer);}privatevoidcommonTest(Consumer<TestItem>consumer){try{CountDownLatchstartSignal=newCountDownLatch(1);CountDownLatchdoneSignal=newCountDownLatch(count);TestItemitem=testItemService.initStock(1L,8);for(inti=0;i<count;++i){Actionaction=()->consumer.accept(item);newThread(newWorker(startSignal,doneSignal,action)).start();}//letallthreadsproceedstartSignal.countDown();doneSignal.await();System.out.println("Allprocessorsdone.Shutdownconnection");}catch(Exceptione){log.error("",e);}}}其中,每次启动的线程数为10,然后,commonTest(Consumer<TestItem>consumer)为这⾥及之后所有测试⽤例的公共代码,所以抽象出来了,⽽Consumer<TestItem>consumer才是测试⽤例间不同的逻辑,并且每次都会把库存初始化为8。1.这⾥省略了各种配置⽂件和配置类等。2.下⽂的所有截图,有⼀处单词拼写错误,reststock写成了resetstock,还请将就着看。启动测试⽤例,可以看到类似如下的控制台打印:testPlainLockName理论上应该只有8个线程能正常扣减库存,⽽结果也与预想的⼀样。这时如果去掉注解@DistributedLock(lockName="1",lockNamePre="item"),会出现什么结果呢?类似如下:withoutDistributedLockannotationtestSpel很明显,上⼀个测试⽤例中,lockName写死为"1"是不可取,⽽是应该取⼊参testItem的id的值,接下来,使⽤SpEL来实现该需求。publicclassTestItemService{@DistributedLock(lockName="#{#testItem.id}",lockNamePre="item")publicIntegertestSpel(TestItemtestItem){TestItemitem=this.getById(testItem.getId());Integerstock=item.getStock();if(stock>0){stock=stock-1;item.setStock(stock);this.saveOrUpdate(item);}else{stock=-1;}returnstock;}}publicclassDistributedLockTests{@TestpublicvoidtestSpel(){Consumer<TestItem>consumer=testItem->{Integerstock=testItemService.testSpel(testItem);if(stock>=0){System.out.println(Thread.currentThread().getName()+":reststock="+stock);}else{System.out.println(Thread.currentThread().getName()+":soldout.");}};commonTest(consumer);}}启动测试⽤例,可以看到控制台类似输出:testSpeltestCheckBeforepublicclassTestItemService@DistributedLock(lockName="#{#testItem.id}",lockNamePre="item",checkBefore="#{#root.target.check(#testItem)}")publicIntegertestCheckBefore(TestItemtestItem){TestItemitem=this.getById(testItem.getId());Integerstock=item.getStock();if(stock>0){stock=stock-1;item.setStock(stock);this.saveOrUpdate(item);}else{stock=-1;}returnstock;}publicvoidcheck(TestItemtestItem){intrandomInt=RandomUtil.randomInt(100,10000);if(randomInt%3==0){System.out.println(String.format("currentthread:%s,randomInt:%d",getCurrentThreadName(),randomInt));CommonResponseEnum.SERVER_BUSY.assertFail();}}}其中,参数checkBefore⽤于在开始加锁前,执⾏某个⽅法进⾏校验,⽐如这⾥,使⽤⽅法check模拟了流量控制,符合⼀定条件时,直接抛异常返回。publicclassDistributedLockTests{@TestpublicvoidtestCheckBefore(){Consumer<TestItem>consumer=testItem->{Integerstock=-1;try{stock=testItemService.testCheckBefore(testItem);}catch(Exceptione){System.out.println(Thread.currentThread().getName()+":系统繁忙");return;}if(stock>=0){System.out.println(Thread.currentThread().getName()+":reststock="+stock);}else{System.out.println(Thread.currentThread().getName()+":soldout.");}};commonTest(consumer);}}启动测试⽤例,类似控制台输出如下:testCheckBefore这⾥有⼀点需要注意,⽅法check必须为public。testTryLock、testFairLock、testWaitTime这⼏个测试⽤例⽐较简单,这⾥就不展⽰了,有兴趣的可以⾃⼰跑⼀下。testLeaseTime⾸先来理解⼀下参数leaseTime的作⽤,即:锁超时时间,超时时间过后,锁⾃动释放。挺好理解的,但重点是,锁被⾃动释放后,之前执⾏的逻辑需要怎么处理。在最后释放锁的时候,发现锁已经不是当前线程持有,有可能已经被其他持有,那之前获得锁后执⾏的逻辑,都变得不可信了,所以理论上需要撤销,如果是数据库操作,那就是回滚。⾸先来看testLeaseTime的第⼀个测试⽤例publicclassTestItemService@DistributedLock(lockName="#{#testItem.id}",lockNamePre="item",leaseTime=2000)publicIntegertestLeaseTime(TestItemtestItem){intci=TestItemService.i.getAndDecrement();if(ci==10){sleep(5000L);("模拟阻塞完成");}else{sleep(300L);}TestItemitem=this.getById(testItem.getId());Integerstock=item.getStock();if(stock>0){stock=stock-1;item.setStock(stock);this.saveOrUpdate(item);}else{stock=-1;}returnstock;}privatevoidsleep(longmillis){try{Thread.sleep(millis);}catch(InterruptedExceptione){e.printStackTrace();}}}publicclassDistributedLockTests{@TestpublicvoidtestLeaseTime(){Consumer<TestItem>consumer=testItem->{Integerstock=testItemService.testLeaseTime(testItem);if(stock>=0){System.out.println(Thread.currentThread().getName()+":reststock="+stock);}else{System.out.println(Thread.currentThread().getName()+":soldout.");}};commonTest(consumer);}}启动测试⽤例,结果类似如下:testLeaseTime这样乍⼀看,看下没什么⽑病,数据库的库存也是正常的,为0。但再细看⼀下代码,模拟阻塞是在⼀开始就进⾏的,那如果把该部分代码挪到从数据库获取到数据之后,会发⽣什么呢?来看第⼆个测试⽤例:publicclassTestItemService/***超卖**@paramtestItem*@return*/@DistributedLock(lockName="#{#testItem.id}",lockNamePre="item",leaseTime=2000)publicIntegertestLeaseTimeOversold(TestItemtestItem){TestItemitem=this.getById(testItem.getId());intci=TestItemService.i.getAndDecrement();if(ci==10){sleep(5000L);("模拟阻塞完成");}else{sleep(300L);}Integerstock=item.getStock();if(stock>0){stock=stock-1;item.setStock(stock);this.saveOrUpdate(item);}else{stock=-1;}returnstock;}}publicclassDistributedLockTests{@TestpublicvoidtestLeaseTimeOversold(){Consumer<TestItem>consumer=testItem->{Integerstock=testItemService.testLeaseTimeOversold(testItem);if(stock>=0){System.out.println(Thread.currentThread().getName()+":reststock="+stock);}else{System.out.println(Thread.currentThread().getName()+":soldout.");}};commonTest(consumer);}}启动测试⽤例,结果类似如下:testLeaseTimeOversold可以看到,很明显超卖了,sold
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 2025年山东沂水城市建设投资集团有限公司招聘笔试参考题库附带答案详解
- 2025年辽宁沈阳产业投资发展集团有限公司招聘笔试参考题库附带答案详解
- 医学基础知识复习必考试题及答案2024年
- 税务师行业动态试题及答案
- 乡村全科执业助理医师考试内容重点试题及答案
- 图书管理员职业发展机会试题及答案
- 2025年健康管理师的职业道德探讨试题及答案
- 教师资格考试批判性思维考查试题及答案
- 基础会计张婕试题及答案
- 星座脾气测试题及答案
- GB/T 13738.2-2017红茶第2部分:工夫红茶
- GB/T 13012-2008软磁材料直流磁性能的测量方法
- GB/T 10004-2008包装用塑料复合膜、袋干法复合、挤出复合
- GA/T 1768-2021移动警务身份认证技术要求
- 贯彻中国式《现代化》全文解读
- 核磁-波普分析课件
- 日本神话课件
- 部编人教版道德与法治四年级下册《合理消费》优质课件
- 大学生中长跑锻炼焦虑心理的原因及对策研究获奖科研报告
- 烟花爆竹安全培训课件
- ABC量表为家长评定量表
评论
0/150
提交评论