文山专业网站建设,做微信投票的网站,单页网站 开元,2023年ppt模板免费一、前言在同一个jvm进程中时#xff0c;可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题#xff0c;但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候#xff0c;juc包的锁就无能无力了#xff0c;这时候就需要分布式锁了。常见…一、前言在同一个jvm进程中时可以使用JUC提供的一些锁来解决多个线程竞争同一个共享资源时候的线程安全问题但是当多个不同机器上的不同jvm进程共同竞争同一个共享资源时候juc包的锁就无能无力了这时候就需要分布式锁了。常见的有使用zk的最小版本redis的set函数数据库锁来实现本节我们谈谈Redis单实例情况下使用set函数来实现分布式锁。二、使用Redis单实例实现分布式锁首先我们来具体看代码package com.jiaduo.DistributedLock;import java.util.Collections;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;public class DistributedLock{private static final String LOCK_SUCCESS OK;private static final String SET_IF_NOT_EXIST NX;private static final String SET_WITH_EXPIRE_TIME PX;private static final Long RELEASE_SUCCESS 1L;private static void validParam(JedisPool jedisPool, String lockKey, String requestId, int expireTime){if (null jedisPool) {throw new IllegalArgumentException(jedisPool obj is null);}if (null lockKey || .equals(lockKey)) {throw new IllegalArgumentException(lock key is blank);}if (null requestId || .equals(requestId)) {throw new IllegalArgumentException(requestId is blank);}if (expireTime 0) {throw new IllegalArgumentException(expireTime is not allowed less zero);}}/**** param jedis* param lockKey* param requestId* param expireTime* return*/public static boolean tryLock(JedisPool jedisPool, String lockKey, String requestId, int expireTime){validParam(jedisPool, lockKey, requestId, expireTime);Jedis jedis null;try {jedis jedisPool.getResource();String result jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);if (LOCK_SUCCESS.equals(result)) {return true;}} catch (Exception e) {throw e;} finally {if (null ! jedis) {jedis.close();}}return false;}/**** param jedis* param lockKey* param requestId* param expireTime*/public static void lock(JedisPool jedisPool, String lockKey, String requestId, int expireTime){validParam(jedisPool, lockKey, requestId, expireTime);while (true) {if (tryLock(jedisPool, lockKey, requestId, expireTime)) {return;}}}/**** param jedis* param lockKey* param requestId* return*/public static boolean unLock(JedisPool jedisPool, String lockKey, String requestId){validParam(jedisPool, lockKey, requestId, 1);String script if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end;Jedis jedis null;try {jedis jedisPool.getResource();Object result jedis.eval(script, Collections.singletonList(lockKey),Collections.singletonList(requestId));if (RELEASE_SUCCESS.equals(result)) {return true;}} catch (Exception e) {throw e;} finally {if (null ! jedis) {jedis.close();}}return false;}}首先Redis的 public String set(final String key, final String value, final String nxxx, final String expx,final int time)方法参数说明其中前面两个是key,value值nxxx为模式这里我们设置为NX意思是说如果key不存在则插入该key对应的value并返回OK否者什么都不做返回null参数expx这里我们设置为PX意思是设置key的过期时间为time 毫秒通过tryLock方法尝试获取锁内部是具体调用Redis的set方法多个线程同时调用tryLock时候会同时调用set方法但是set方法本身是保证原子性的对应同一个key来说多个线程调用set方法时候只有一个线程返回OK其它线程因为key已经存在会返回null所以返回OK的线程就相当与获取到了锁其它返回null的线程则相当于获取锁失败。另外这里我们要保证value(requestId)值唯一是为了保证只有获取到锁的线程才能释放锁,这个下面释放锁时候会讲解。通过lock 方法让使用tryLock获取锁失败的线程本地自旋转重试获取锁这类似JUC里面的CAS。Redis有一个叫做eval的函数支持Lua脚本执行并且能够保证脚本执行的原子性也就是在执行脚本期间其它执行redis命令的线程都会被阻塞。这里解锁时候使用下面脚本if redis.call(get, KEYS[1]) ARGV[1] then return redis.call(del, KEYS[1]) else return 0 end其中keys[1]为unLock方法传递的keyargv[1]为unLock方法传递的requestId;脚本redis.call(‘get’, KEYS[1])的作用是获取key对应的value值这里会返回通过Lock方法传递的requetId, 然后看当前传递的RequestId是否等于key对应的值等于则说明当前要释放锁的线程就是获取锁的线程则继续执行redis.call(‘del’, KEYS[1])脚本删除key对应的值。三、总结本文使用redis单实例结合redis的set方法和eval函数实现了一个简单的分布式锁但是这个实现还是明显有问题的。虽然使用set方法设置了超时时间以避免线程获取到锁后redis挂了后锁没有被释放的情况但是超时时间设置为多少合适那如果设置太小可能会存在线程获取锁后执行业务逻辑时间大于锁超时时间那么就会存在逻辑还没执行完锁已经因为超时自动释放了而其他线程可能获取到锁那么之前获取锁的线程的业务逻辑的执行就没有保证原子性。另外还有一个问题是Lock方法里面是自旋调用tryLock进行重试这就会导致像JUC中的AtomicLong一样在高并发下多个线程竞争同一个资源时候造成大量线程占用cpu进行重试操作。这时候其实可以随机生成一个等待时间等时间到后在进行重试以减少潜在的同时对一个资源进行竞争的并发量。最后想了解JDK NIO和更多Netty基础的可以单击我想了解更多关于粘包半包问题单击我更多关于分布式系统中服务降级策略的知识可以单击 单击我想系统学dubbo的单击我想学并发的童鞋可以 单击我