Contents
  1. 1. 实现源码:
  2. 2. 实现方式
  3. 3. 悲观锁的实现
    1. 3.1. lock()

实现源码:

https://github.com/huangzhenshi/DistributeLearning/tree/master/DistributeLearning-master

实现方式

乐观锁、悲观锁的方式

  1. 乐观锁实现原理:通过watch存货量变量,先做第一轮判断,存货大于0,开启事务,减库存,然后提交事务,如果存货发生变动,就操作失效,循环操作直到成功为止,或者是存量为负
    public void run() {
    while (true) {
    System.out.println("顾客:" + clientName + "开始抢商品");
    jedis = RedisUtil.getInstance().getJedis();
    try {
    jedis.watch(key);
    int prdNum = Integer.parseInt(jedis.get(key));// 当前商品个数
    if (prdNum > 0) {
    Transaction transaction = jedis.multi();
    transaction.set(key, String.valueOf(prdNum - 1));
    List<Object> result = transaction.exec();
    if (result == null || result.isEmpty()) {
    System.out.println("悲剧了,顾客:" + clientName + "没有抢到商品");// 可能是watch-key被外部修改,或者是数据操作被驳回
    } else {
    jedis.sadd(clientList, clientName);// 抢到商品记录一下
    System.out.println("好高兴,顾客:" + clientName + "抢到商品");
    System.out.println("account is: "+ prdNum);
    break;
    }
    } else {
    System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品");
    break;
    }
    } catch (Exception e) {
    e.printStackTrace();
    } finally {
    jedis.unwatch();
    RedisUtil.returnResource(jedis);
    }
    }
    }

悲观锁的实现

通过redis的setnx方法,多个线程对同一个key操作,一次只有一个线程会成功,来实现锁操作,解锁通过jedis.del(lockKey)。另外引入expireTime的参数,来解决异常线程占用锁,使该方法更健壮。

  • 类似于Zookeeper的实现原理,需要一个辅助类RedisBasedDistributedLock来实现 tryLock和lock、unlock等操作
  • 但是这里的逻辑是非公平锁的形式去抢锁,每个用户循环操作,先读取存量,如果大于0就有3s的时间去抢锁,抢锁失败然后进入下一个循环,这样设计防止库存为0了,其它未抢到锁的线程还不知道。
  • 抢锁成功的话,再校验一次存货,仍然大于0,就消费一个库存,添加消费者,最后释放锁,结束循环
  • 引入了锁超时机制,即使当前线程出现异常,未成功释放锁,也不会让当前业务永远停摆
  • 抢锁的过程也为在超时时限内循环抢锁,而且是抢2次,第一次通过jedis.setnx(lockKey, stringOfLockExpireTime) == 1来抢,这是主要的获取锁的方式
  • 如果正常获取不到锁,再通过String oldValue = jedis.getSet(lockKey, stringOfLockExpireTime);来判断当前拥有锁的线程是否超时了,因为是原子操作,如果超时了,只有1个线程可以获取到超时的oldValue
  • 释放锁的逻辑也很暴力,直接删除: jedis.del(lockKey);
  • 其实也可以通过 jedis.setnx(lockKey,Expire,lockName)的形式实现自动的超时判断,而不需要额外的代码进行判断,更简洁
    锁的过期时间是5s,初始化的时候设置了
    public void init() {
    jedis = RedisUtil.getInstance().getJedis();
    redisBasedDistributedLock = new RedisBasedDistributedLock(jedis, "lock.lock", 5 * 1000);
    }
    public void run() {
    while (true) {
    if(Integer.valueOf(jedis.get(key))<= 0) {
    break;
    }
    //缓存还有商品,取锁,商品数目减去1
    System.out.println("顾客:" + clientName + "开始抢商品");
    if (redisBasedDistributedLock.tryLock(3,TimeUnit.SECONDS)) { //等待3秒获取锁,否则返回false
    int prdNum = Integer.valueOf(jedis.get(key)); //再次取得商品缓存数目
    if (prdNum > 0) {
    jedis.decr(key);//商品数减1
    jedis.sadd(clientList, clientName);// 抢到商品记录一下
    System.out.println("好高兴,顾客:" + clientName + "抢到商品");
    } else {
    System.out.println("悲剧了,库存为0,顾客:" + clientName + "没有抢到商品");
    }
    redisBasedDistributedLock.unlock();
    break;
    }
    }
    //释放资源
    redisBasedDistributedLock = null;
    RedisUtil.returnResource(jedis);
    }

lock()

  • 设置3s的最长抢锁时间,也是循环抢锁
  • 引入了锁超时机制,即使当前线程出现异常,未成功释放锁,也不会让当前业务永远停摆
  • 获取锁成功,会设置locked = true;和setExclusiveOwnerThread(Thread.currentThread());
    lock(true, time, unit, false);
    // 阻塞式获取锁的实现
    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 useless
    while (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 expired
    System.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;
    }

lock的优化方案:不通过去isTimeExpired判断持锁线程是否过期,而是获取到锁的时候,就自动设置过期时间,来降低CPU的消耗

protected boolean lock(boolean useTimeout, long time, TimeUnit unit, boolean interrupt) throws InterruptedException {
//开始抢锁的时间
long start = System.currentTimeMillis();
//超时时间,这里是3s
long timeout = unit.toMillis(time); // if !useTimeout, then it's useless
while (useTimeout ? isTimeout(start, timeout) : true) {
if (interrupt) {
checkInterruption();
}
//引入了锁超时机制,即使当前线程出现异常,未成功释放锁,也不会让当前业务永远停摆
long lockExpireTime = System.currentTimeMillis() + lockExpires + 1;// 锁超时时间
String stringOfLockExpireTime = String.valueOf(lockExpireTime);
if (jedis.setnx(lockKey, stringOfLockExpireTime) == 1) { // 获取到锁
jedis.setex("lockKey", EXPIRE_SECOND, stringOfLockExpireTime);
//成功获取到锁, 设置相关标识
locked = true;
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
}
return false;
}

Contents
  1. 1. 实现源码:
  2. 2. 实现方式
  3. 3. 悲观锁的实现
    1. 3.1. lock()