SSM (十五) 乐观锁与悲观锁的实际应用(上)

本文涉及的产品
云数据库 Tair(兼容Redis),内存型 2GB
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
简介: 随着互联网的兴起,现在三高(高可用、高性能、高并发)项目是越来越流行。本次来谈谈高并发。首先假设一个业务场景:数据库中有一条数据,需要获取到当前的值,在当前值的基础上+10,然后再更新回去。

前言


随着互联网的兴起,现在三高(高可用、高性能、高并发)项目是越来越流行。


本次来谈谈高并发。首先假设一个业务场景:数据库中有一条数据,需要获取到当前的值,在当前值的基础上+10,然后再更新回去。


如果此时有两个线程同时并发处理,第一个线程拿到数据是10,+10=20更新回去。第二个线程原本是要在第一个线程的基础上再+20=40,结果由于并发访问取到更新前的数据为10,+20=30


这就是典型的存在中间状态,导致数据不正确。来看以下的例子:


并发所带来的问题


和上文提到的类似,这里有一张price表,表结构如下:


CREATE TABLE `price` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `total` decimal(12,2) DEFAULT '0.00' COMMENT '总值',
  `front` decimal(12,2) DEFAULT '0.00' COMMENT '消费前',
  `end` decimal(12,2) DEFAULT '0.00' COMMENT '消费后',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1268 DEFAULT CHARSET=utf8


我这里写了一个单测:就一个主线程,循环100次,每次把front的值减去10,再写入一次流水记录,正常情况是写入的每条记录都会每次减去10。


/**
     * 单线程消费
     */
    @Test
    public void singleCounsumerTest1(){
        for (int i=0 ;i<100 ;i++){
            Price price = priceMapper.selectByPrimaryKey(1);
            int ron = 10 ;
            price.setFront(price.getFront().subtract(new BigDecimal(ron)));
            price.setEnd(price.getEnd().add(new BigDecimal(ron)));
            price.setTotal(price.getFront().add(price.getEnd()));
            priceMapper.updateByPrimaryKey(price) ;
            price.setId(null);
            priceMapper.insertSelective(price) ;
        }
    }


执行结果如下:


01.png


可以看到确实是每次都递减10。


但是如果是多线程的情况下会是如何呢:


我这里新建了一个PriceController


/**
     * 线程池 无锁
     * @param redisContentReq
     * @return
     */
    @RequestMapping(value = "/threadPrice",method = RequestMethod.POST)
    @ResponseBody
    public BaseResponse<NULLBody> threadPrice(@RequestBody RedisContentReq redisContentReq){
        BaseResponse<NULLBody> response = new BaseResponse<NULLBody>() ;
        try {
            for (int i=0 ;i<10 ;i++){
                Thread t = new Thread(new Runnable() {
                    @Override
                    public void run() {
                        Price price = priceMapper.selectByPrimaryKey(1);
                        int ron = 10 ;
                        price.setFront(price.getFront().subtract(new BigDecimal(ron)));
                        price.setEnd(price.getEnd().add(new BigDecimal(ron)));
                        priceMapper.updateByPrimaryKey(price) ;
                        price.setId(null);
                        priceMapper.insertSelective(price) ;
                    }
                });
                config.submit(t);
            }
            response.setReqNo(redisContentReq.getReqNo());
            response.setCode(StatusEnum.SUCCESS.getCode());
            response.setMessage(StatusEnum.SUCCESS.getMessage());
        }catch (Exception e){
            logger.error("system error",e);
            response.setReqNo(response.getReqNo());
            response.setCode(StatusEnum.FAIL.getCode());
            response.setMessage(StatusEnum.FAIL.getMessage());
        }
        return response ;
    }


其中为了节省资源使用了一个线程池:


@Component
public class ThreadPoolConfig {
    private static final int MAX_SIZE = 10 ;
    private static final int CORE_SIZE = 5;
    private static final int SECOND = 1000;
    private ThreadPoolExecutor executor ;
    public ThreadPoolConfig(){
        executor = new ThreadPoolExecutor(CORE_SIZE,MAX_SIZE,SECOND, TimeUnit.MICROSECONDS,new LinkedBlockingQueue<Runnable>()) ;
    }
    public void submit(Thread thread){
        executor.submit(thread) ;
    }
}


关于线程池的使用今后会仔细探讨。这里就简单理解为有10个线程并发去处理上面单线程的逻辑,来看看结果怎么样:


02.png


会看到明显的数据错误,导致错误的原因自然就是有线程读取到了中间状态进行了错误的更新。


进而有了以下两种解决方案:悲观锁和乐观锁。


相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
5月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的代驾应用系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的代驾应用系统附带文章和源代码部署视频讲解等
201 21
|
5月前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的服装品牌的推广及应用网站附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的服装品牌的推广及应用网站附带文章和源代码部署视频讲解等
50 4
|
5月前
|
Java 测试技术 数据安全/隐私保护
基于ssm+vue.js+uniapp小程序的《数据库原理及应用》课程平台附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的《数据库原理及应用》课程平台附带文章和源代码部署视频讲解等
38 0
基于ssm+vue.js+uniapp小程序的《数据库原理及应用》课程平台附带文章和源代码部署视频讲解等
|
6月前
|
JavaScript Java 测试技术
基于ssm+vue.js的绿色农产品推广应用网站附带文章和源代码设计说明文档ppt
基于ssm+vue.js的绿色农产品推广应用网站附带文章和源代码设计说明文档ppt
59 4
|
消息中间件 监控 Kafka
SSM(十七) MQ应用(下)
写这篇文章的起因是由于之前的一篇关于Kafka异常消费,当时为了解决问题不得不使用临时的方案。 总结起来归根结底还是对Kafka不熟悉导致的,加上平时工作的需要,之后就花些时间看了Kafka相关的资料。
|
消息中间件 Kafka API
SSM(十七) MQ应用(中)
写这篇文章的起因是由于之前的一篇关于Kafka异常消费,当时为了解决问题不得不使用临时的方案。 总结起来归根结底还是对Kafka不熟悉导致的,加上平时工作的需要,之后就花些时间看了Kafka相关的资料。
|
消息中间件 算法 Java
SSM(十七) MQ应用(上)
写这篇文章的起因是由于之前的一篇关于Kafka异常消费,当时为了解决问题不得不使用临时的方案。 总结起来归根结底还是对Kafka不熟悉导致的,加上平时工作的需要,之后就花些时间看了Kafka相关的资料。
|
SQL 关系型数据库 MySQL
SSM (十五) 乐观锁与悲观锁的实际应用(下)
随着互联网的兴起,现在三高(高可用、高性能、高并发)项目是越来越流行。 本次来谈谈高并发。首先假设一个业务场景:数据库中有一条数据,需要获取到当前的值,在当前值的基础上+10,然后再更新回去。
|
缓存 Java 数据库连接
SSM(九) 反射的实际应用 - 构建日志对象
相信做Java的童鞋或多或少都听过反射,这也应该是Java从入门到进阶的必经之路。 但是在我们的实际开发中直接使用它们的几率貌似还是比较少的,(除了造轮子或者是Spring Mybatis这些框架外)。 所以这里介绍一个在实际开发中还是小有用处的反射实例。
|
3月前
|
Java 数据库连接 Maven
手把手教你如何搭建SSM框架、图书商城系统案例
这篇文章是关于如何搭建SSM框架以及实现一个图书商城系统的详细教程,包括了项目的配置文件整合、依赖管理、项目结构和运行效果展示,并提供了GitHub源码链接。
手把手教你如何搭建SSM框架、图书商城系统案例