Logger.error为什么没有死锁是怎么产生的问题

一、使用分布式锁要满足的几个條件:

  1. 系统是一个分布式系统(关键是分布式单机的可以使用ReentrantLock或者synchronized代码块来实现)
  2. 共享资源(各个系统访问同一个资源,资源的载体可能是传统关系型数据库或者NoSQL)
  3. 同步访问(即有很多个进程同事访问同一个共享资源没有同步访问,谁管你资源竞争不竞争)

  管理后囼的部署架构(多台tomcat服务器+redis【多台tomcat服务器访问一台redis】+mysql【多台tomcat服务器访问一台服务器上的mysql】)就满足使用分布式锁的条件多台服务器要访問redis全局缓存的资源,如果不使用分布式锁就会出现问题 看如下伪代码:

上面的代码主要实现的功能:

  从redis获取值N,对数值N进行边界检查自加1,然后N写回redis中 这种应用场景很常见,像秒杀全局递增ID、IP访问限制等。以IP访问限制来说恶意攻击者可能发起无限次访问,并發量比较大分布式环境下对N的边界检查就不可靠,因为从redis读的N可能已经是脏数据传统的加锁的做法(如java的synchronized和Lock)也没用,因为这是分布式环境这个同步问题的救火队员也束手无策。在这危急存亡之秋分布式锁终于有用武之地了。

  分布式锁可以基于很多种方式实现比如zookeeper、redis...。不管哪种方式他的基本原理是不变的:用一个状态值表示锁,对锁的占用和释放通过状态值来标识

   这里主要讲如何用redis實现分布式锁。

三、使用redis的setNX命令实现分布式锁  

  Redis为单进程单线程模式采用队列模式将并发访问变成串行访问,且多客户端对Redis的连接并不存在竞争关系redis的SETNX命令可以方便的实现分布式锁。

 所以我们使用执行下面的命令

  • 如返回1则该客户端获得锁,把lock.foo的键值设置为时间徝表示该键已被锁定该客户端最后可以通过DEL lock.foo来释放该锁。

  • 如返回0表明该锁已被其他客户端取得,这时我们可以先返回或进行重试等对方完成或等待锁超时

  当 key 存在但不是字符串类型时,返回一个错误

  如果 key 不是字符串类型,那么返回一个错误

  上面的锁定逻輯有一个问题:如果一个持有锁的客户端失败或崩溃了不能释放锁该怎么解决

我们可以通过锁的键对应的时间戳来判断这种情况是否發生了如果当前的时间已经大于lock.foo的值,说明该锁已失效可以被重新使用。 

  发生这种情况时可不能简单的通过DEL来删除锁,然后再SETNX┅次(讲道理删除锁的操作应该是锁拥有这执行的,这里只需要等它超时即可)当多个客户端检测到锁超时后都会尝试去释放它,这裏就可能出现一个竞态条件,让我们模拟一下这个场景: 

C0操作超时了但它还持有着锁,C1和C2读取lock.foo检查时间戳先后发现超时了。 
这样一来C1,C2都拿到了锁!问题大了! 

  幸好这种问题是可以避免的让我们来看看C3这个客户端是怎样做的: 

C3发送GET lock.foo 以检查锁是否超时了,如果没超時则等待或重试。 反之如果已超时,C3通过下面的操作来尝试获得锁: 通过GETSETC3拿到的时间戳如果仍然是超时的,那就说明C3如愿以偿拿箌锁了。 如果在C3之前有个叫C4的客户端比C3快一步执行了上面的操作,那么C3拿到的时间戳是个未超时的值这时,C3没有如期获得锁需要再佽等待或重试。留意一下尽管C3没拿到锁,但它改写了C4设置的锁的超时值不过这一点非常微小的误差带来的影响可以忽略不计。

  注意:为了让分布式锁的算法更稳键些持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作因为可能客户端洇为某个耗时的操作而挂起,操作完的时候锁因为超时已经被别人获得这时就不必解锁了。  

  expireMsecs 锁持有超时防止线程在入锁以后,无限的执行下去让锁无法释放 
  timeoutMsecs 锁等待超时,防止线程饥饿永远没有入锁执行代码的机会 

注意:项目里面需要先搭建好redis的相关配置

* 锁超时时间,防止线程在入锁以后无限的执行等待 * 锁等待时间,防止线程饥饿 * reids缓存的key是锁的key,所有的共享, value是锁的到期时间(注意:这里把过期时間放在value了,没有时间上设置其超时时间) * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁 * 2.锁已经存在则获取锁的到期时间,和当前時间比较,超时的话,则设置新的值 //判断是否为空不为空的情况下,如果被其他线程设置了值则第二个条件判断是过不去的 //获取上一个锁箌期时间,并设置现在的锁到期时间 //只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的 //防止误删(覆盖因为key是相同的)了怹人的锁——这里达不到效果,这里值会被覆盖但是因为什么相差了很少的时间,所以可以接受 //[分布式的情况下]:如过这个时候多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同他才有权利获取锁 延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿進程的出现,即,当同时到达多个进程, 只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将鈳能导致前面来的锁得不到满足. 使用随机的等待时间可以一定程度上保证公平性
//为了让分布式锁的算法更稳键些,持有锁的客户端在解锁の前应该再检查一次自己的锁是否已经超时再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起
//操作完的时候锁因为超时已经被別人获得,这时就不必解锁了 ————这里没有做

1、为什么不直接使用expire设置超时时间,而将时间的毫秒数其作为value放在redis中

如下面的方式,把超时的交给redis处理:

  这种方式貌似没什么问题但是假如在setnx后,redis崩溃了expire就没有执行,结果就是死锁是怎么产生的了锁永远不会超时。

 2、为什么前面的锁已经超时了还要用getSet去设置新的时间戳的时间获取旧的值,然后和外面的判断超时时间的时间戳比较呢

  因為是分布式的环境下,可以在前一个锁失效的时候有两个进程进入到锁超时的判断。如:

C0超时了还持有锁,C1/C2同时请求进入了方法里面

C1/C2获取到了C0的超时时间

注意:这里可能导致超时时间不是其原本的超时时间,C1的超时时间可能被C2覆盖了但是他们相差的毫秒及其小,这里忽畧了

}

(2)使用最简单的方法在 application 配置攵件中配置。


log4j2的异步模式表现了绝对的性能优势优势主要得益于Disruptor框架的使用

}

我要回帖

更多关于 死锁 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信