Redis之事务乐观锁和Jedis,谁说的Redis不能实现乐观锁的?(三)

我是方圆,励志做一名优秀的博主
祝大家54青年节快乐!

3. 事务

3.1 必备的知识

  1. Redis单条命令保证原子性,但是事务不保证原子性
  2. 事务的本质:是一组命令的集合,在事务执行的过程中,按照顺序执行
  3. Redis没有事务的隔离级别,不会出现脏读、误读等情况
  4. 所有的命令在事务中,只有发起执行命令(exec)后才会执行
  5. 事务的基本命令 ↓
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> set name xiaowang #命令入队
QUEUED
127.0.0.1:6379> exec #执行事务
1) OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> discard #放弃这个事务
OK

3.2 事务遭受异常时的两种情况

3.2.1 编译时异常

  • 若出现的是编译时异常,也就是说,代码写的有问题,那么整个事务都不会被执行

3.2.2 运行时异常

  • 若出现的是语法问题,那么事务中的该命令报错,其他命令正常执行,这里我们也能看出它不保证原子性

3.3 用Redis事务实现乐观锁

3.3.1 先来铺垫一下概念,虽然大家对它们已经很熟悉了

  1. 悲观锁:这块儿锁很悲观,它认为什么时候都会出现问题,所以它干什么事儿都要加上锁,效率也就很慢了。
  2. 乐观锁:相反,这个锁它很乐观,认为什么时候不会出现问题,对数据进行更新的时候只是去验证一下数据的“版本”,检查在执行命令的期间,这个数据有没有被修改过(也是著名的ABA问题)

3.3.2 Redis代码实现

我来简单的解释下这个例子,money变量作为我们手里的钱,out变量代表我们花出去的钱,我们来实现"花钱"的事务

  1. 在正常情况下,在事务执行前没有其他线程对monry变量进行修改
127.0.0.1:6379> set money 100 #开始我们有100元
OK
127.0.0.1:6379> set out 0 #花出去0元
OK
127.0.0.1:6379> watch money #这里采取监视功能,保证乐观锁的实现
OK
127.0.0.1:6379> multi #开启“花钱”事务
OK
127.0.0.1:6379> DECRBY money 20 #花钱20
QUEUED
127.0.0.1:6379> INCRBY out 20 
QUEUED
127.0.0.1:6379> exec #执行后,正常输出
1) (integer) 80
2) (integer) 20
  1. 下面就要演示乐观锁了,我们再开一个线程,在执行“花钱”事务之前对monry变量进行修改(下面两个线程,如图所示)
    在这里插入图片描述

重点注意了,线程1开启事务后,没有执行(exec)事务

127.0.0.1:6379> unwatch #我们先把之前的监视释放掉
OK
127.0.0.1:6379> watch money #重新加上监视
OK
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30
QUEUED
127.0.0.1:6379> INCRBY out 30 
QUEUED
# 到这里我们没有执行事务

我们在线程2中,对money变量进行修改,那么修改之后,再让线程1执行事务,应该出现花钱事务失败的预期

127.0.0.1:6379> INCRBY money 50 #增加50元
(integer) 130

好,下面我们让线程1中执行事务,重点看最后的代码

127.0.0.1:6379> watch money 
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> DECRBY money 30
QUEUED
127.0.0.1:6379> INCRBY out 30
QUEUED
127.0.0.1:6379> exec #重点看这里!它失败了!看来我们成功实现了乐观锁!哈哈哈哈
(nil)
  1. 上面的就是乐观锁问题,若想解决,就要释放监视,重新加上监视即可(这也就是保证了,我们在事务执行的过程中,操作的数据没有被改变过)
#接上方代码继续
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> unwatch #先把之前的监视释放了
OK
127.0.0.1:6379> watch money #加上新的监视
OK
#此时我们应该知道,在线程2过后,money变量是130元
127.0.0.1:6379> multi #开启事务
OK
127.0.0.1:6379> DECRBY money 30 #花30
QUEUED
127.0.0.1:6379> INCRBY out 30 
QUEUED
127.0.0.1:6379> exec #执行事务,发现正常输出
1) (integer) 100
2) (integer) 50

好勒,乐观锁就实现了,没事儿的话,要自己敲一边哦~

4. Jedis

使用Java操作Redis

4.1 导包

        <dependency>
            <groupId>redis.clients</groupId>
            <artifactId>jedis</artifactId>
            <version>3.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

4.2 简单的代码实现

  1. 测试连接
import redis.clients.jedis.Jedis;

public class TestPing {

    public static void main(String[] args) {

        Jedis jedis = new Jedis("此处为IP地址",6379);
        System.out.println(jedis.ping());
    }
}

输出结果为PONG,连接成功
在这里插入图片描述
2. 简单代码测试

import redis.clients.jedis.Jedis;

public class Test {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("此处为IP地址",6379);

        //String类型
        jedis.set("name","zhangsan");
        System.out.println("输出的值应该为zhangsan:"+jedis.get("name"));

        //Hash类型
        jedis.hset("myhash","field1","1");
        System.out.println("输出的值应该为1:"+jedis.hget("myhash", "field1"));

        //Set类型
        jedis.sadd("mykey","1","2","3");
        System.out.println("输出的值应该为1,2,3:"+jedis.smembers("mykey"));

        //List类型
        jedis.lpush("list","1","3","5");
        System.out.println("输出的值应该为5,3,1:"+jedis.lrange("list", 0, -1));
    }
}

代码测试结果如下
在这里插入图片描述

4.3 用Jedis实现事务

import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;

public class TransactionTest {
    public static void main(String[] args) {
        Jedis jedis = new Jedis("此处为IP地址",6379);

        //开启事务
        Transaction multi = jedis.multi();

        try{
            multi.set("num1","1");
            multi.set("num2","2");
            //执行任务
            multi.exec();
            System.out.println("应该输出2:" + jedis.get("num2"));
        }catch (Exception e){
            System.out.println("发生错误,放弃事务");
            multi.discard();
        }
        finally {
        	//关闭事务
            multi.close();
        }
    }
}

输出结果如下
在这里插入图片描述
在这里插入图片描述

参考

狂神说Redis

该系列其他文章

Redis之必备基础知识点,文读百变其意自现(一)
Redis之数据类型,好记性不如烂笔头(二)
Redis之redis.conf解析,了解了这些配置信息,才能说了解Redis(四)
Redis之主从复制和哨兵模式,差不多儿啦(五)
Redis之RDB和AOF持久化机制详解

原创文章 56 获赞 19 访问量 6027

猜你喜欢

转载自blog.csdn.net/qq_46225886/article/details/105917993