1.背景如果客户端获得锁,处理时间超过最大约定时间,或者在锁持有期间发生故障,无法主动释放持有的锁也可以正确释放通过其他机制,保证以后其他客户端也能锁定,整个处理流程继续正常执行。简单说明一下:客户端抢到分布式锁后开始执行任务,执行完释放分布式锁。持有锁后由于客户端异常未能释放锁,将导致锁成为永恒锁。为了避免这种情况,在创建锁的时候为锁指定一个过期时间。到期后,锁会自动删除。从这个角度来说,是对锁资源的一种保护。2、原因还乱吗?逻辑很简单明了,但凡事都有两个方面。自动删除自然是有道理的,但肯定有坏处。要想使锁的功能坚固耐用,就必须理清思路,在不断的自我反省和自我反省中寻找答案。我认为这是一种内省式的学习模式。我想在未来尝试这种模式。让我们一起试试吧。:问:锁过期会被删除,但是任务还没结束怎么办?如果释放锁时任务还没有完成,可能会导致其他客户端再次抢到锁,任务会重复执行。问题:锁过期时间应该设置多长时间?逻辑听起来不错,如果你能确定任务的最大持续时间,那很好;大多数时候很难确定任务的最长持续时间。问题:锁的过期时间应该是多长?反正都会放行,过期时间要设置的足够长;如果频繁使用锁,锁程序有bug无法释放,服务器端岂不是会产生大量垃圾数据?想了想,对于健壮的分布式锁来说,过期时间设置太长不合适,设置太短也不合适。问题:如何平衡?不长不短,主动延期!在持锁期间,可以适当顺延锁的到期时间。对于基于Redis的分布式锁,需要调用API重置锁key的过期时间。当前线程持有锁后,在任务执行过程中不能再调用API重试锁key的过期时间。问题:谁来调用API?需要一个不同的线程来执行更新。问题:给每个锁分配一个线程?是的,如果在使用分布式锁的场景下没有并发,一个client同时只有三两把锁,那是没有问题的。每次成功抢到锁后,都会启动一个线程,通过线程中的循环来更新锁。publicvoidrun(){while(true){//续租action.run();}}问题:更新的执行频率是多少?有一些例程处理租约续租间隔默认为过期时间的1/3。如果把锁的到期时间设置的和实际耗时相差不大,那么一两次续租基本就可以满足大部分情况了。问题:为什么需要触发续费操作?这不是浪费资源吗?使用过期时间1/3的间隔。如果自定义锁在3秒后过期,则每秒都会有一个续订命令。你觉得不合适?问题:您想避免过于频繁的续订指令吗?需要避免过于频繁地调用更新命令,也可以增加一个最小的更新间隔,比如最小5秒。续费周期可由用户控制,无需发起续费调用。比如大部分任务执行时间是5秒,那么锁是7秒,刷新时间设置为6秒,那么6秒内任务结束就不需要再刷新任务,即,没必要把过期时间设置得太长,也没必要执行一两次续费操作。问题:如何实现续租的间隔?线程间的时间间隔控制通常是通过sleep()方法来实现的。如果再精确一点,单位就是毫秒。publicvoidrun(){while(true){//1.间隔TimeUnit.MILLISECONDS.sleep(sleepTime);//2.续租action.run();}}问题:是否应该关闭线程?在释放锁的时候,需要主动关闭负责更新的线程,所以线程循环中必须有一个变量来控制while循环的退出publicvoidrun(){while(isRunning){//1.间隔TimeUnit.MILLISECONDS.sleep(sleepTime);//2.续租action.run();}}问题:变量是跨线程访问的,如何保证跨线程可见性?将volatile关键字添加到变量。privatevolatilebooleanisRunning=true;voidcancel(){//控制线程退出this.isRunning=true;}问题:如果更新线程在sleep()中,是否要等待sleep()结束?如果一直等到sleep()结束,会造成资源浪费:能不能快速结束sleep()状态?是的,通过interrupt(),注意中断时会抛出异常InterruptedExceptionvoidcancel(){//控制线程退出this.isRunning=true;//中断线程this.interrupt();}到这里,好像理直气壮了。3、新思考题:如果同时有几百上千把锁怎么办?有成百上千个线程同时工作。如果你觉得没有问题,也没有问题,那好吧,不继续看下一篇了。那我们该怎么办呢?您可以使用Executors.newScheduledThreadPool,其中包含scheduleAtFixedRate。阿里Java代码规范不允许Execurots?不能用?有什么风险?你不累吗?
