标签:第一个 开始 str ati public 循环 exp 单线程 oid
为什么会有这个需求:
例如一个简单用户的操作,一个线程去修改用户状态,首先在在内存中读出用户的状态,然后在内存中进行修改,然后在存到数据库中。在单线程中,这是没有问题的。但是在多线程中由于读取,修改,写入是三个操作,不是原子操作(同时成功或失败),因此在多线程中会存在数据的安全性问题。
这个问题的话,就可以用分布式锁在限制程序的并发执行。
实现思路:
就是进来一个先占位,当别的线程进来操作的时候,发现有人占位了,就会放弃或者稍后再试。
占位的实现:
在redis中的setnx命令来实现,redis命令可以参考我这篇博客https://www.cnblogs.com/javazl/p/12657280.html,默认set命令就是存值,当key存在的时候,set就会覆盖key的value值,而setnx则不会。当没有key的时候,setnx就会进来先占位,当key存在了,其他的setnx就进不来了。。等到第一个执行完成后,在del命令释放位子。
代码实现:
public class LockTest { public static void main(String[] args) { Redis redis = new Redis(); redis.execute(jedis->{ Long setnx = jedis.setnx("k1", "v1"); //setnx的返回值为long类型 if (setnx == 1) { //没人占位 jedis.set("name", "zl"); String name = jedis.get("name"); System.out.println(name); //释放资源 jedis.del("k1"); }else{ //有人占位,停止/暂缓 操作 } }); } }
上边代码中,就是一个简易的分布式锁的实现,但是有一个问题。就是如果在占位后释放前挂了。那么这个线程会一直释放不了,也就是del命令没有调用,后面的全部请求都阻塞到这里,锁就变成了死锁。因此这里需要去优化。
优化的方法就是加过期时间,确保锁在一定时间后能够释放.
public class LockTest { public static void main(String[] args) { Redis redis = new Redis(); redis.execute(jedis->{ Long setnx = jedis.setnx("k1", "v1"); if (setnx == 1) { //给锁添加一个过期时间,防止应用在运行过程中抛出异常导致锁无法及时得到释放 jedis.expire("k1", 5); //没人占位 jedis.set("name", "zl"); String name = jedis.get("name"); System.out.println(name); jedis.del("k1"); }else{ //有人占位,停止/暂缓 操作 } }); }
这样处理后,就可以保证锁可以正常的释放。但是会有一个新的问题,就是如果在取锁和设置过期时间服务器挂掉了,因为取锁,也就是setnx和设置过期时间是两个操作,不具备原子性所以不可能同时完成。这个锁就会被一直占用,无法得到释放,成为死锁。那么如何解决呢?
在redis2.8之后,setnx和expireke可以通过一个命令一起执行,让两个操作变成一个,就会解决这个问题。
优化实现:
public class LockTest { public static void main(String[] args) { Redis redis = new Redis(); redis.execute(jedis->{ //将两个操作合并成一个,nx就是setnx,ex就是expire String set = jedis.set("k1", "v1", new SetParams().nx().ex(5)); //操作结果为okhuo或者error if (set !=null && "OK".equals(set)) { //给锁添加一个过期时间,防止应用在运行过程中抛出异常导致锁无法及时得到释放 jedis.expire("k1", 5); //没人占位 jedis.set("name", "zl); String name = jedis.get("name"); System.out.println(name); //释放资源 jedis.del("k1"); }else{ //有人占位,停止/暂缓 操作 } }); } }
用过期时间优化后,虽然解决了死锁的问题,但是又有一个新的问题产生,就是超时问题:
举个例子:如果要执行的业务很耗时,可能会出现紊乱,当地一个线程获取到锁的时候,开始执行业务代码,但是业务代码很耗时,假如过期时间是3秒,而业务执行需要5秒,这样,锁就会提前释放,然后第二个线程获取到锁并开始执行。当执行到第2秒的时候,第一个锁也执行完了,此时第一个线程会释放第二个线程的锁,然后第三个线程继续获取锁并执行,当到第3秒的时候第二个线程执行完了,那么又会提前释放锁,一直如此循环,会造成线程的紊乱。
那么解决的思路主要有两种
标签:第一个 开始 str ati public 循环 exp 单线程 oid
原文地址:https://www.cnblogs.com/javazl/p/12661730.html