摘要:本文要实现的是一种使用redis来实现分布式锁。

本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning

1、分布式锁

分布式锁在是一种用来安全访问分式式机器上变量的安全方案,一般用在全局id生成,秒杀系统,全局变量共享、分布式事务等。一般会有两种实现方案,一种是悲观锁的实现,一种是乐观锁的实现。悲观锁的并发性能差,但是能保证不会发生脏数据的可能性小一点。

2、Redis命令介绍
使用Redis实现分布式锁,有两个重要函数需要介绍

SETNX命令(SET if Not eXists)
语法:
SETNX key value
功能:
当且仅当 key 不存在,将 key 的值设为 value ,并返回1;若给定的 key 已经存在,则 SETNX 不做任何动作,并返回0。

GETSET命令(这是一个原子命令!)
语法:
GETSET key value
功能:
将给定 key 的值设为 value ,并返回 key 的旧值 (old value),当 key 存在但不是字符串类型时,返回一个错误,当key不存在时,返回nil。

GET命令
语法:
GET key
功能:
返回 key 所关联的字符串值,如果 key 不存在那么返回特殊值 nil 。

DEL命令
语法:
DEL key [KEY …]
功能:
删除给定的一个或多个 key ,不存在的 key 会被忽略。

3、代码实现

(1)AbstractLock基类

package com.github.distribute.lock.redis;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;/*** 锁的骨架实现, 真正的获取锁的步骤由子类去实现.* **/
public abstract class AbstractLock implements Lock {/*** <pre>* 这里需不需要保证可见性值得讨论, 因为是分布式的锁, * 1.同一个jvm的多个线程使用不同的锁对象其实也是可以的, 这种情况下不需要保证可见性 * 2.同一个jvm的多个线程使用同一个锁对象, 那可见性就必须要保证了.* </pre>*/protected volatile boolean locked;/*** 当前jvm内持有该锁的线程(if have one)*/private Thread exclusiveOwnerThread;public void lock() {try {lock(false, 0, null, false);} catch (InterruptedException e) {// TODO ignore}}public void lockInterruptibly() throws InterruptedException {lock(false, 0, null, true);}public boolean tryLock(long time, TimeUnit unit) {try {System.out.println("ghggggggggggggg");return lock(true, time, unit, false);} catch (InterruptedException e) {e.printStackTrace();System.out.println("" + e);}return false;}public boolean tryLockInterruptibly(long time, TimeUnit unit) throws InterruptedException {return lock(true, time, unit, true);}public void unlock() {// TODO 检查当前线程是否持有锁if (Thread.currentThread() != getExclusiveOwnerThread()) {throw new IllegalMonitorStateException("current thread does not hold the lock");}unlock0();setExclusiveOwnerThread(null);}protected void setExclusiveOwnerThread(Thread thread) {exclusiveOwnerThread = thread;}protected final Thread getExclusiveOwnerThread() {return exclusiveOwnerThread;}protected abstract void unlock0();/*** 阻塞式获取锁的实现* * @param useTimeout* @param time* @param unit* @param interrupt*            是否响应中断* @return* @throws InterruptedException*/protected abstract boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt)throws InterruptedException;}

(2)、实现类

package com.github.distribute.lock.redis;import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;import redis.clients.jedis.Jedis;/*** 基于Redis的SETNX操作实现的分布式锁* * 获取锁时最好用lock(long time, TimeUnit unit), 以免网路问题而导致线程一直阻塞*/
public class RedisBasedDistributedLock extends AbstractLock {private Jedis jedis;// 锁的名字protected String lockKey;// 锁的有效时长(毫秒)protected long lockExpires;public RedisBasedDistributedLock(Jedis jedis, String lockKey, long lockExpires) {this.jedis = jedis;this.lockKey = lockKey;this.lockExpires = lockExpires;}// 阻塞式获取锁的实现protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException {System.out.println("test1");if (interrupt) {checkInterruption();}System.out.println("test2");long start = System.currentTimeMillis();long timeout = unit.toMillis(time); // if !useTimeout, then it's uselesswhile (useTimeout ? isTimeout(start, timeout) : true) {System.out.println("test3");if (interrupt) {checkInterruption();}long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;// 锁超时时间String stringOfLockExpireTime = String.valueOf(lockExpireTime);System.out.println("test4");if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁System.out.println("test5");//成功获取到锁, 设置相关标识locked = true;setExclusiveOwnerThread(Thread.currentThread());return true;}System.out.println("test6");String value = jedis.get(lockKey);if (value != null && isTimeExpired(value)) { // lock is expiredSystem.out.println("test7");// 假设多个线程(非单jvm)同时走到这里String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); //原子操作// 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)// 加入拿到的oldValue依然是expired的,那么就说明拿到锁了System.out.println("test8");if (oldValue != null && isTimeExpired(oldValue)) {System.out.println("test9");//成功获取到锁, 设置相关标识locked = true;setExclusiveOwnerThread(Thread.currentThread());return true;}} else {// TODO lock is not expired, enter next loop retrying}}System.out.println("test10");return false;}public boolean tryLock() {long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;// 锁超时时间String stringOfLockExpireTime = String.valueOf(lockExpireTime);if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁// 成功获取到锁, 设置相关标识locked = true;setExclusiveOwnerThread(Thread.currentThread());return true;}String value = jedis.get(lockKey);if (value != null && isTimeExpired(value)) { // lock is expired// 假设多个线程(非单jvm)同时走到这里String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime); //原子操作// 但是走到这里时每个线程拿到的oldValue肯定不可能一样(因为getset是原子性的)// 假如拿到的oldValue依然是expired的,那么就说明拿到锁了if (oldValue != null && isTimeExpired(oldValue)) {//成功获取到锁, 设置相关标识locked = true;setExclusiveOwnerThread(Thread.currentThread());return true;}} else {// TODO lock is not expired, enter next loop retrying}return false;}/*** Queries if this lock is held by any thread.* * @return {@code true} if any thread holds this lock and {@code false}*         otherwise*/public boolean isLocked() {if (locked) {return true;} else {String value = jedis.get(lockKey);// TODO 这里其实是有问题的, 想:当get方法返回value后, 假设这个value已经是过期的了,// 而就在这瞬间, 另一个节点set了value, 这时锁是被别的线程(节点持有), 而接下来的判断// 是检测不出这种情况的.不过这个问题应该不会导致其它的问题出现, 因为这个方法的目的本来就// 不是同步控制, 它只是一种锁状态的报告.return !isTimeExpired(value);}}@Overrideprotected void unlock0() {// 判断锁是否过期String value = jedis.get(lockKey);if (!isTimeExpired(value)) {doUnlock();}}private void checkInterruption() throws InterruptedException {if (Thread.currentThread().isInterrupted()) {throw new InterruptedException();}}private boolean isTimeExpired(String value) {return Long.parseLong(value) < System.currentTimeMillis();}private boolean isTimeout(long start, long timeout) {return start + timeout > System.currentTimeMillis();}private void doUnlock() {jedis.del(lockKey);}public Condition newCondition() {// TODO Auto-generated method stubreturn null;}}

原理其实很简单,就是利用setNx和getSet这两个命令来实现。

SetNx如果返回为1,表示拿到锁,并设置超时失效时间。

getSet是一个原子操作,它是在锁超时后没释放会进入,这时有可能多个应用一同时进入,但是如果设置成功,会返回oldValue,如果两个oldvalue一样,表明拿到锁了

上面使用到的redis工具类

package com.github.distribute.lock.redis;import java.util.List;
import java.util.Map;
import java.util.Set;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import redis.clients.jedis.BinaryClient.LIST_POSITION;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;public class RedisUtil {private static final Logger LOGGER = LoggerFactory.getLogger(RedisUtil.class);private static JedisPool pool = null;private static RedisUtil ru = new RedisUtil();public static void main(String[] args) {RedisUtil redisUtil = RedisUtil.getInstance();redisUtil.set("test", "test");LOGGER.info(redisUtil.get("test"));}private RedisUtil() {if (pool == null) {String ip = "10.75.202.11";int port = 6379;JedisPoolConfig config = new JedisPoolConfig();// 控制一个pool可分配多少个jedis实例,通过pool.getResource()来获取;// 如果赋值为-1,则表示不限制;如果pool已经分配了maxActive个jedis实例,则此时pool的状态为exhausted(耗尽)。config.setMaxTotal(10000);// 控制一个pool最多有多少个状态为idle(空闲的)的jedis实例。config.setMaxIdle(2000);// 表示当borrow(引入)一个jedis实例时,最大的等待时间,如果超过等待时间,则直接抛出JedisConnectionException;config.setMaxWaitMillis(1000 * 100);config.setTestOnBorrow(true);pool = new JedisPool(config, ip, port, 100000);}}public Jedis getJedis() {Jedis jedis = pool.getResource();return jedis;}public static RedisUtil getInstance() {return ru;}/*** <p>* 通过key获取储存在redis中的value* </p>* <p>* 并释放连接* </p>* * @param key* @return 成功返回value 失败返回null*/public String get(String key) {Jedis jedis = null;String value = null;try {jedis = pool.getResource();value = jedis.get(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return value;}/*** <p>* 向redis存入key和value,并释放连接资源* </p>* <p>* 如果key已经存在 则覆盖* </p>* * @param key* @param value* @return 成功 返回OK 失败返回 0*/public String set(String key, String value) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.set(key, value);} catch (Exception e) {LOGGER.error(e.getMessage());return "0";} finally {returnResource(pool, jedis);}}/*** <p>* 删除指定的key,也可以传入一个包含key的数组* </p>* * @param keys*            一个key 也可以使 string 数组* @return 返回删除成功的个数*/public Long del(String... keys) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.del(keys);} catch (Exception e) {LOGGER.error(e.getMessage());return 0L;} finally {returnResource(pool, jedis);}}/*** <p>* 通过key向指定的value值追加值* </p>* * @param key* @param str* @return 成功返回 添加后value的长度 失败 返回 添加的 value 的长度 异常返回0L*/public Long append(String key, String str) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.append(key, str);} catch (Exception e) {LOGGER.error(e.getMessage());return 0L;} finally {returnResource(pool, jedis);}return res;}/*** <p>* 判断key是否存在* </p>* * @param key* @return true OR false*/public Boolean exists(String key) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.exists(key);} catch (Exception e) {LOGGER.error(e.getMessage());return false;} finally {returnResource(pool, jedis);}}/*** <p>* 设置key value,如果key已经存在则返回0,nx==> not exist* </p>* * @param key* @param value* @return 成功返回1 如果存在 和 发生异常 返回 0*/public Long setnx(String key, String value) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.setnx(key, value);} catch (Exception e) {LOGGER.error(e.getMessage());return 0L;} finally {returnResource(pool, jedis);}}/*** <p>* 设置key value并制定这个键值的有效期* </p>* * @param key* @param value* @param seconds*            单位:秒* @return 成功返回OK 失败和异常返回null*/public String setex(String key, String value, int seconds) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.setex(key, seconds, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key 和offset 从指定的位置开始将原先value替换* </p>* <p>* 下标从0开始,offset表示从offset下标开始替换* </p>* <p>* 如果替换的字符串长度过小则会这样* </p>* <p>* example:* </p>* <p>* value : bigsea@zto.cn* </p>* <p>* str : abc* </p>* <P>* 从下标7开始替换 则结果为* </p>* <p>* RES : bigsea.abc.cn* </p>* * @param key* @param str* @param offset*            下标位置* @return 返回替换后 value 的长度*/public Long setrange(String key, String str, int offset) {Jedis jedis = null;try {jedis = pool.getResource();return jedis.setrange(key, offset, str);} catch (Exception e) {LOGGER.error(e.getMessage());return 0L;} finally {returnResource(pool, jedis);}}/*** <p>* 通过批量的key获取批量的value* </p>* * @param keys*            string数组 也可以是一个key* @return 成功返回value的集合, 失败返回null的集合 ,异常返回空*/public List<String> mget(String... keys) {Jedis jedis = null;List<String> values = null;try {jedis = pool.getResource();values = jedis.mget(keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return values;}/*** <p>* 批量的设置key:value,可以一个* </p>* <p>* example:* </p>* <p>* obj.mset(new String[]{"key2","value1","key2","value2"})* </p>* * @param keysvalues* @return 成功返回OK 失败 异常 返回 null**/public String mset(String... keysvalues) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.mset(keysvalues);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 批量的设置key:value,可以一个,如果key已经存在则会失败,操作会回滚* </p>* <p>* example:* </p>* <p>* obj.msetnx(new String[]{"key2","value1","key2","value2"})* </p>* * @param keysvalues* @return 成功返回1 失败返回0*/public Long msetnx(String... keysvalues) {Jedis jedis = null;Long res = 0L;try {jedis = pool.getResource();res = jedis.msetnx(keysvalues);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 设置key的值,并返回一个旧值* </p>* * @param key* @param value* @return 旧值 如果key不存在 则返回null*/public String getset(String key, String value) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.getSet(key, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过下标 和key 获取指定下标位置的 value* </p>* * @param key* @param startOffset*            开始位置 从0 开始 负数表示从右边开始截取* @param endOffset* @return 如果没有返回null*/public String getrange(String key, int startOffset, int endOffset) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.getrange(key, startOffset, endOffset);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key 对value进行加值+1操作,当value不是int类型时会返回错误,当key不存在是则value为1* </p>* * @param key* @return 加值后的结果*/public Long incr(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.incr(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key给指定的value加值,如果key不存在,则这是value为该值* </p>* * @param key* @param integer* @return*/public Long incrBy(String key, Long integer) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.incrBy(key, integer);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 对key的值做减减操作,如果key不存在,则设置key为-1* </p>* * @param key* @return*/public Long decr(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.decr(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 减去指定的值* </p>* * @param key* @param integer* @return*/public Long decrBy(String key, Long integer) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.decrBy(key, integer);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取value值的长度* </p>* * @param key* @return 失败返回null*/public Long serlen(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.strlen(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key给field设置指定的值,如果key不存在,则先创建* </p>* * @param key* @param field*            字段* @param value* @return 如果存在返回0 异常返回null*/public Long hset(String key, String field, String value) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.hset(key, field, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key给field设置指定的值,如果key不存在则先创建,如果field已经存在,返回0* </p>* * @param key* @param field* @param value* @return*/public Long hsetnx(String key, String field, String value) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.hsetnx(key, field, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key同时设置 hash的多个field* </p>* * @param key* @param hash* @return 返回OK 异常返回null*/public String hmset(String key, Map<String, String> hash) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.hmset(key, hash);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key 和 field 获取指定的 value* </p>* * @param key* @param field* @return 没有返回null*/public String hget(String key, String field) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.hget(key, field);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key 和 fields 获取指定的value 如果没有对应的value则返回null* </p>* * @param key* @param fields*            可以使 一个String 也可以是 String数组* @return*/public List<String> hmget(String key, String... fields) {Jedis jedis = null;List<String> res = null;try {jedis = pool.getResource();res = jedis.hmget(key, fields);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key给指定的field的value加上给定的值* </p>* * @param key* @param field* @param value* @return*/public Long hincrby(String key, String field, Long value) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.hincrBy(key, field, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key和field判断是否有指定的value存在* </p>* * @param key* @param field* @return*/public Boolean hexists(String key, String field) {Jedis jedis = null;Boolean res = false;try {jedis = pool.getResource();res = jedis.hexists(key, field);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回field的数量* </p>* * @param key* @return*/public Long hlen(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.hlen(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key 删除指定的 field* </p>* * @param key* @param fields*            可以是 一个 field 也可以是 一个数组* @return*/public Long hdel(String key, String... fields) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.hdel(key, fields);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回所有的field* </p>* * @param key* @return*/public Set<String> hkeys(String key) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.hkeys(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回所有和key有关的value* </p>* * @param key* @return*/public List<String> hvals(String key) {Jedis jedis = null;List<String> res = null;try {jedis = pool.getResource();res = jedis.hvals(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取所有的field和value* </p>* * @param key* @return*/public Map<String, String> hgetall(String key) {Jedis jedis = null;Map<String, String> res = null;try {jedis = pool.getResource();res = jedis.hgetAll(key);} catch (Exception e) {// TODO} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key向list头部添加字符串* </p>* * @param key* @param strs*            可以使一个string 也可以使string数组* @return 返回list的value个数*/public Long lpush(String key, String... strs) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.lpush(key, strs);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key向list尾部添加字符串* </p>* * @param key* @param strs*            可以使一个string 也可以使string数组* @return 返回list的value个数*/public Long rpush(String key, String... strs) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.rpush(key, strs);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key在list指定的位置之前或者之后 添加字符串元素* </p>* * @param key* @param where*            LIST_POSITION枚举类型* @param pivot*            list里面的value* @param value*            添加的value* @return*/public Long linsert(String key, LIST_POSITION where, String pivot, String value) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.linsert(key, where, pivot, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key设置list指定下标位置的value* </p>* <p>* 如果下标超过list里面value的个数则报错* </p>* * @param key* @param index*            从0开始* @param value* @return 成功返回OK*/public String lset(String key, Long index, String value) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.lset(key, index, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key从对应的list中删除指定的count个 和 value相同的元素* </p>* * @param key* @param count*            当count为0时删除全部* @param value* @return 返回被删除的个数*/public Long lrem(String key, long count, String value) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.lrem(key, count, value);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key保留list中从strat下标开始到end下标结束的value值* </p>* * @param key* @param start* @param end* @return 成功返回OK*/public String ltrim(String key, long start, long end) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.ltrim(key, start, end);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key从list的头部删除一个value,并返回该value* </p>* * @param key* @return*/synchronized public String lpop(String key) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.lpop(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key从list尾部删除一个value,并返回该元素* </p>* * @param key* @return*/synchronized public String rpop(String key) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.rpop(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key从一个list的尾部删除一个value并添加到另一个list的头部,并返回该value* </p>* <p>* 如果第一个list为空或者不存在则返回null* </p>* * @param srckey* @param dstkey* @return*/public String rpoplpush(String srckey, String dstkey) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.rpoplpush(srckey, dstkey);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取list中指定下标位置的value* </p>* * @param key* @param index* @return 如果没有返回null*/public String lindex(String key, long index) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.lindex(key, index);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回list的长度* </p>* * @param key* @return*/public Long llen(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.llen(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取list指定下标位置的value* </p>* <p>* 如果start 为 0 end 为 -1 则返回全部的list中的value* </p>* * @param key* @param start* @param end* @return*/public List<String> lrange(String key, long start, long end) {Jedis jedis = null;List<String> res = null;try {jedis = pool.getResource();res = jedis.lrange(key, start, end);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key向指定的set中添加value* </p>* * @param key* @param members*            可以是一个String 也可以是一个String数组* @return 添加成功的个数*/public Long sadd(String key, String... members) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.sadd(key, members);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key删除set中对应的value值* </p>* * @param key* @param members*            可以是一个String 也可以是一个String数组* @return 删除的个数*/public Long srem(String key, String... members) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.srem(key, members);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key随机删除一个set中的value并返回该值* </p>* * @param key* @return*/public String spop(String key) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.spop(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取set中的差集* </p>* <p>* 以第一个set为标准* </p>* * @param keys*            可以使一个string 则返回set中所有的value 也可以是string数组* @return*/public Set<String> sdiff(String... keys) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.sdiff(keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取set中的差集并存入到另一个key中* </p>* <p>* 以第一个set为标准* </p>* * @param dstkey*            差集存入的key* @param keys*            可以使一个string 则返回set中所有的value 也可以是string数组* @return*/public Long sdiffstore(String dstkey, String... keys) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.sdiffstore(dstkey, keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取指定set中的交集* </p>* * @param keys*            可以使一个string 也可以是一个string数组* @return*/public Set<String> sinter(String... keys) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.sinter(keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取指定set中的交集 并将结果存入新的set中* </p>* * @param dstkey* @param keys*            可以使一个string 也可以是一个string数组* @return*/public Long sinterstore(String dstkey, String... keys) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.sinterstore(dstkey, keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回所有set的并集* </p>* * @param keys*            可以使一个string 也可以是一个string数组* @return*/public Set<String> sunion(String... keys) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.sunion(keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回所有set的并集,并存入到新的set中* </p>* * @param dstkey* @param keys*            可以使一个string 也可以是一个string数组* @return*/public Long sunionstore(String dstkey, String... keys) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.sunionstore(dstkey, keys);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key将set中的value移除并添加到第二个set中* </p>* * @param srckey*            需要移除的* @param dstkey*            添加的* @param member*            set中的value* @return*/public Long smove(String srckey, String dstkey, String member) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.smove(srckey, dstkey, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取set中value的个数* </p>* * @param key* @return*/public Long scard(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.scard(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key判断value是否是set中的元素* </p>* * @param key* @param member* @return*/public Boolean sismember(String key, String member) {Jedis jedis = null;Boolean res = null;try {jedis = pool.getResource();res = jedis.sismember(key, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取set中随机的value,不删除元素* </p>* * @param key* @return*/public String srandmember(String key) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.srandmember(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取set中所有的value* </p>* * @param key* @return*/public Set<String> smembers(String key) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.smembers(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key向zset中添加value,score,其中score就是用来排序的* </p>* <p>* 如果该value已经存在则根据score更新元素* </p>* * @param key* @param score* @param member* @return*/public Long zadd(String key, double score, String member) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zadd(key, score, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key删除在zset中指定的value* </p>* * @param key* @param members*            可以使一个string 也可以是一个string数组* @return*/public Long zrem(String key, String... members) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zrem(key, members);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key增加该zset中value的score的值* </p>* * @param key* @param score* @param member* @return*/public Double zincrby(String key, double score, String member) {Jedis jedis = null;Double res = null;try {jedis = pool.getResource();res = jedis.zincrby(key, score, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回zset中value的排名* </p>* <p>* 下标从小到大排序* </p>* * @param key* @param member* @return*/public Long zrank(String key, String member) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zrank(key, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回zset中value的排名* </p>* <p>* 下标从大到小排序* </p>* * @param key* @param member* @return*/public Long zrevrank(String key, String member) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zrevrank(key, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key将获取score从start到end中zset的value* </p>* <p>* socre从大到小排序* </p>* <p>* 当start为0 end为-1时返回全部* </p>* * @param key* @param start* @param end* @return*/public Set<String> zrevrange(String key, long start, long end) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.zrevrange(key, start, end);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回指定score内zset中的value* </p>* * @param key* @param max* @param min* @return*/public Set<String> zrangebyscore(String key, String max, String min) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.zrevrangeByScore(key, max, min);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回指定score内zset中的value* </p>* * @param key* @param max* @param min* @return*/public Set<String> zrangeByScore(String key, double max, double min) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.zrevrangeByScore(key, max, min);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 返回指定区间内zset中value的数量* </p>* * @param key* @param min* @param max* @return*/public Long zcount(String key, String min, String max) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zcount(key, min, max);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key返回zset中的value个数* </p>* * @param key* @return*/public Long zcard(String key) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zcard(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key获取zset中value的score值* </p>* * @param key* @param member* @return*/public Double zscore(String key, String member) {Jedis jedis = null;Double res = null;try {jedis = pool.getResource();res = jedis.zscore(key, member);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key删除给定区间内的元素* </p>* * @param key* @param start* @param end* @return*/public Long zremrangeByRank(String key, long start, long end) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zremrangeByRank(key, start, end);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key删除指定score内的元素* </p>* * @param key* @param start* @param end* @return*/public Long zremrangeByScore(String key, double start, double end) {Jedis jedis = null;Long res = null;try {jedis = pool.getResource();res = jedis.zremrangeByScore(key, start, end);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 返回满足pattern表达式的所有key* </p>* <p>* keys(*)* </p>* <p>* 返回所有的key* </p>* * @param pattern* @return*/public Set<String> keys(String pattern) {Jedis jedis = null;Set<String> res = null;try {jedis = pool.getResource();res = jedis.keys(pattern);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** <p>* 通过key判断值得类型* </p>* * @param key* @return*/public String type(String key) {Jedis jedis = null;String res = null;try {jedis = pool.getResource();res = jedis.type(key);} catch (Exception e) {LOGGER.error(e.getMessage());} finally {returnResource(pool, jedis);}return res;}/*** 返还到连接池** @param pool* @param jedis*/public static void returnResource(JedisPool pool, Jedis jedis) {if (jedis != null) {pool.returnResourceObject(jedis);}}/*** 返还到连接池** @param pool* @param jedis*/public static void returnResource(Jedis jedis) {if (jedis != null) {pool.returnResourceObject(jedis);}}
}

4、测试

写了一个简单的秒杀系统的模拟

package com.github.distribute.lock.redis;import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;import redis.clients.jedis.Jedis;public class PessimisticLockTest {public static void main(String[] args) {long starTime=System.currentTimeMillis();initPrduct();initClient();printResult();long endTime=System.currentTimeMillis();long Time=endTime-starTime;System.out.println("程序运行时间: "+Time+"ms");   }/*** 输出结果*/public static void printResult() {Jedis jedis = RedisUtil.getInstance().getJedis();Set<String> set = jedis.smembers("clientList");int i = 1;for (String value : set) {System.out.println("第" + i++ + "个抢到商品," + value + " ");}RedisUtil.returnResource(jedis);}/** 初始化顾客开始抢商品*/public static void initClient() {ExecutorService cachedThreadPool = Executors.newCachedThreadPool();int clientNum = 10000;// 模拟客户数目for (int i = 0; i < clientNum; i++) {cachedThreadPool.execute(new PessClientThread(i));}cachedThreadPool.shutdown();while (true) {if (cachedThreadPool.isTerminated()) {System.out.println("所有的线程都结束了!");break;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}}/*** 初始化商品个数*/public static void initPrduct() {int prdNum = 100;// 商品个数String key = "prdNum";String clientList = "clientList";// 抢购到商品的顾客列表Jedis jedis = RedisUtil.getInstance().getJedis();if (jedis.exists(key)) {jedis.del(key);}if (jedis.exists(clientList)) {jedis.del(clientList);}jedis.set(key, String.valueOf(prdNum));// 初始化RedisUtil.returnResource(jedis);}}/*** 顾客线程* * @author linbingwen**/
class PessClientThread implements Runnable {String key = "prdNum";// 商品主键String clientList = "clientList";// // 抢购到商品的顾客列表主键String clientName;RedisBasedDistributedLock redisBasedDistributedLock;Jedis jedis = null;public PessClientThread(int num) {clientName = "编号=" + num;init();}public void init() {jedis = RedisUtil.getInstance().getJedis();redisBasedDistributedLock = new RedisBasedDistributedLock(jedis, "lock.lock", 5 * 1000);}public void run() {try {Thread.sleep((int) (Math.random() * 5000));// 随机睡眠一下} catch (InterruptedException e1) {}while (true) {//先判断缓存是否有商品if(Integer.valueOf(jedis.get(key))<= 0) {break;}//缓存还有商品,取锁,商品数目减去1System.out.println("顾客:" + clientName + "开始抢商品");if (redisBasedDistributedLock.tryLock(3,TimeUnit.SECONDS)) { //等待3秒获取锁,否则返回falseint prdNum = Integer.valueOf(jedis.get(key)); //再次取得商品缓存数目if (prdNum > 0) {jedis.decr(key);//商品数减1jedis.sadd(clientList, clientName);// 抢到商品记录一下System.out.println("好高兴,顾客:" + clientName + "抢到商品");} else {System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品");}redisBasedDistributedLock.unlock();break;}}//释放资源redisBasedDistributedLock = null;RedisUtil.returnResource(jedis);}}

输出结果:

本文源码请在这里下载:https://github.com/appleappleapple/DistributeLearning

更多技术细节请关注的笔者的公众号"单例模式":

Redis分布式锁----悲观锁实现,以秒杀系统为例相关推荐

  1. 电商库存锁_解密 Redis 助力双 11 背后电商秒杀系统

    作者:AlibabaCloud 来源:https://github.com/AlibabaCloudDocs/kvstore/blob/master/cn.zh-CN/最佳实践/使用%20Redis% ...

  2. 【Redis】事物和锁机制乐观锁悲观锁

    目录 1. Redis 的事务定义 2. Multi.Exec.discard 3. 事务的错误处理 4. 事务冲突的问题 悲观锁 乐观锁 1. Redis 的事务定义 Redis 事务是一个单独的隔 ...

  3. Java锁详解:“独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁+线程锁”

    在Java并发场景中,会涉及到各种各样的锁如公平锁,乐观锁,悲观锁等等,这篇文章介绍各种锁的分类: 公平锁/非公平锁 可重入锁 独享锁/共享锁 乐观锁/悲观锁 分段锁 自旋锁 线程锁 乐观锁 VS 悲 ...

  4. Mysql之乐观锁悲观锁:乐观锁检查数据状态 悲观锁更新时锁定数据

    1.问题来源 就是一数据表的数据  在两个人同时修改的时候  会出现混乱 例子:如一个字段记录status=1 表示可以下单  货品只有1个的时候    a下单的同时b也下单 : a有修改status ...

  5. Django - ORM - 事务, 乐观锁, 悲观锁

    事务 概念 Transaction 事务:一个最小的不可再分的工作单元:通常一个事务对应一个完整的业务(例如银行账户转账业务,该业务就是一个最小的工作单元) 一个完整的业务需要批量的DML(inser ...

  6. mysql默认乐观锁悲观锁_MySQL中悲观锁和乐观锁到底是什么?-阿里云开发者社区...

    索引和锁是数据库中的两个核心知识点,隔离级别的实现都是通过锁来完成的 按照锁颗粒对锁进行划分 ? 锁用来对数据进行锁定,我们可以从锁定对象的粒度大小来对锁进行划分,分别为行锁.页锁和表锁. 行锁就是按 ...

  7. 最全Java锁详解:独享锁/共享锁+公平锁/非公平锁+乐观锁/悲观锁

    在Java并发场景中,会涉及到各种各样的锁,比如:高并发编程系列:4种常用Java线程锁的特点,性能比较.使用场景,这些锁有对应的种类:公平锁,乐观锁,悲观锁等等,这篇文章来详细介绍各种锁的分类: 公 ...

  8. MySQL - 行锁 表锁 乐观锁 悲观锁 读锁 写锁

    MySQL - 行锁 表锁 乐观锁 悲观锁 读锁 写锁 锁是在执行多线程时用于强行限制资源访问的同步机制,即用于在并发控制中保证对互斥要求的满足.在DBMS中,可以按照锁的粒度把数据库锁分为行级锁(I ...

  9. 可重入锁/不可重入锁,公平锁/非公平锁,乐观锁/悲观锁,独享锁/共享锁,偏向锁/轻量级锁/重量级锁,分段锁,自旋锁

    在并发编程中,会涉及到各种各样的锁,这篇文章主要介绍各种锁的分类以及作用. 介绍的内容如下: 可重入锁/不可重入锁 公平锁/非公平锁 乐观锁/悲观锁 独享锁/共享锁 偏向锁/轻量级锁/重量级锁 分段锁 ...

  10. Java 面试 :乐观锁 悲观锁

    乐观锁悲观锁,是为了解决多线程并发操作共享变量可能导致的脏读.幻读和不可重复读等问题 悲观锁 悲观锁,是因为这是一种对数据的修改持有悲观态度的并发控制方式.总是假设最坏的情况,每次读取数据的时候都默认 ...

最新文章

  1. python软件安装-学python安装的软件总结
  2. 使用Windows Mobile Device Center进行手机的同步
  3. error LNK2001: unresolved external symbol _WinMain@16
  4. C#通过SSH连接MySql
  5. spring使用JdbcTemplate和jdbcDaosupport及具名参数使用
  6. WPF: 结束程序及关闭所有窗口
  7. 【实践】Angel深度学习在腾讯广告推荐训练优化中的实践
  8. 分布式发布订阅模型网络的实现有哪些
  9. 功能强大且易用的云打印解决方案
  10. Windows平板 区分当前是鼠标点击还是触摸
  11. compaq 515 安装声卡驱动IDT后耳机无声音
  12. 关于计算机的好处的英语作文,关于电脑好处的英语作文
  13. 深度学习 Deep Learning简介 (二):浅层学习(Shallow Learning)和深度学习(Deep Learning)
  14. 华硕X370 Pro更新BIOS后黑屏自救记录
  15. 常见的SSL证书错误代码及解决方法
  16. 利用原生写js满天星星
  17. killall杀死nginx顽固进程
  18. Android系统Recovery工作原理之使用update.zip升级过程分析(一)---update.zip包的制作【转】...
  19. 关于图灵测试和中文房间的一些思考
  20. macbook pro 怎么设置分屏_小米Pro要不要整黑苹果——Hackintosh浅度体验记录

热门文章

  1. C++——第一章 基本语言
  2. node.js是用来做什么的?
  3. 织梦安装之后需要做哪些安全操作
  4. 2021年保研夏令营经验贴
  5. HOG人体特征提取+SVM分类器训练进行人体检测
  6. 区域银行的数字「进化论」
  7. 建议大家试一试这几款软件
  8. Mac不能写入U盘的解决方法
  9. TokenGazer 深度研究 | RSKRIF:技术生态均有良好进展 采用状况继续观察
  10. mysql 中的包含函数