高并发下Service层的写法

最近在项目里遇到一个坑,先上简易版的描述:每次从库里查询一下库存余量,每次购买一个商品。

数据库:

store为库存量。

service层代码:

@Override
    public synchronized void sell() {
        System.out.println("<======"+System.currentTimeMillis());

        //根据局id获取商品信息
        Goods goods = goodDao.findOne(1);
        //获取当前库存
        int store = goods.getStore();
        System.out.println(Thread.currentThread().getName()+" begin:"+store);

        if(store - 1 >= 0) {
            store = store -1;
            goods.setStore(store);
            //save当前余量
            Goods save = goodDao.save(goods);
            System.out.println(Thread.currentThread().getName()+" end:"+save.getStore());
        }
        System.out.println(System.currentTimeMillis()+"========>");

    }

在这段代码里,因为加了synchronized进行修饰,所以无论多少个线程过来,只会有一个线程对锁住的代码块进行操作,那么,库存始终减1,那么这样是没有问题的。

接下来,如果加入@Transactional,开启声明式事务,那么就会有坑了。

@Override
    @Transactional
    public synchronized void sell() {
        System.out.println("<======"+System.currentTimeMillis());

        //根据局id获取商品信息
        Goods goods = goodDao.findOne(1);//获取当前库存
        int store = goods.getStore();
        System.out.println(Thread.currentThread().getName()+" begin:"+store);

        if(store - 1 >= 0) {
            store = store -1;
            goods.setStore(store);
            //展示库存-1后的余量
            Goods save = goodDao.save(goods);
            //TODO 可能对其他表进行了操作....
            System.out.println(Thread.currentThread().getName()+" end:"+save.getStore());
        }
        System.out.println(System.currentTimeMillis()+"========>");

    }

由于加入了@Transactional,那么就会当做一个事务来进行处理。如果并发的去执行,那么会库存扣减不一致。原因在于,第一个线程执行完成以后,aop的方法还在继续,需要去commit,这个需要一定的时间。然后这个时候代码块已经走完了,释放了锁,那下一个线程过来去库里查,还是commit前的库存数量,所以,导致该问题。

解决办法是自定义一个查询方法,使用select ... for update的方式,给这条数据加上锁。JPA的repositry里的写法:

//PESSIMISTIC_WRITE:事务开始即获得数据库的锁
    @Lock(value=LockModeType.PESSIMISTIC_WRITE)
    @Query(value = "select t from Goods t where t.id =?1 ")
    Goods queryById(Integer id);

那么就ok了,原理是这样的: 在第一个线程进来的时候,开启了一个事务,给当前这行数据加了一个行锁,然后在代码执行到最后的时候,虽然jvm里的锁会释放,第二个线程会进来,但是会卡在select for update这里,因为第一个事务还没有提交,所以行锁还在。直到第一个事务提交了以后,第二个线程才会继续执行,查询到数据,这个时候的数据,一定是commit完成以后的数据了。那就不会有脏数据的发生。

这次问题的主要原因是JVM锁与@Transactional声明式事务aop没法同时执行的原因导致的。所以使用编程式事务是不存在上述问题的(我试过)。

原文地址:https://www.cnblogs.com/TravisGrady/p/10669840.html

时间: 2024-08-03 21:58:16

高并发下Service层的写法的相关文章

项目中Service层的写法

截取自项目中的一个service实现类,记录一下: base类 1 package com.bupt.auth.service.base; 2 3 import javax.annotation.Resource; 4 5 import com.bupt.auth.dao.OauthClientDao; 6 import com.bupt.auth.dao.PermissionDao; 7 import com.bupt.auth.dao.ResourceDao; 8 import com.bu

面试实战考核:设计一个高并发下的下单功能

功能需求:设计一个秒杀系统 初始方案 商品表设计:热销商品提供给用户秒杀,有初始库存. @Entity public class SecKillGoods implements Serializable{ @Id private String id; /** * 剩余库存 */ private Integer remainNum; /** * 秒杀商品名称 */ private String goodsName; } 秒杀订单表设计:记录秒杀成功的订单情况 @Entity public clas

java Web项目Service层通用接口和entityVo对象与entity对象转化问题的解决方案

Service层的接口中有一些比较常用方法,一次又一次的在新的Service层中被书写,所以懒惰的程序员又烦了,他们决定写个通用接口来解决这个问题. 有些项目中,实体类即承担接收表单数据的任务,又承担持久化任务,很省心.但有些项目中这两项任务的执行类不是同一个,一个Entity.java来执行数据 持久化的任务,一个EntityVo.java类来执行接收表单数据的任务.那么问题来了:Service层需要的是entityVo对象,而DAO层需要的是entity对象,这两个对象 会有一些相同的属性和

DAO层,Service层,Controller层、View层介绍

来自:http://jonsion.javaeye.com/blog/592335 DAO层 DAO 层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此 接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及 有关数据库连接的参数都在Spring的配置文件中进行配置. Service层 Service 层主要负责业

php结合redis实现高并发下的抢购、秒杀功能

原文: http://blog.csdn.net/nuli888/article/details/51865401 抢购.秒杀是如今很常见的一个应用场景,主要需要解决的问题有两个:1 高并发对数据库产生的压力2 竞争状态下如何解决库存的正确减少("超卖"问题)对于第一个问题,已经很容易想到用缓存来处理抢购,避免直接操作数据库,例如使用Redis.重点在于第二个问题 常规写法: 查询出对应商品的库存,看是否大于0,然后执行生成订单等操作,但是在判断库存是否大于0处,如果在高并发下就会有问

Java实现高并发秒杀API--Service层2

今天完成了整个Java实现高并发秒杀API--Service层的学习: 1.接口的编码以及实现类的逻辑编写 2.利用spring ioc对Service进行管理 3.利用spring声明式事务对事务进行控制: 事务主要配置: <!--配置事务管理器 -->    <bean id="transactionManager"        class="org.springframework.jdbc.datasource.DataSourceTransacti

DAO层,Service层,Controller层、View层

DAO层:DAO层主要是做数据持久层的工作,负责与数据库进行联络的一些任务都封装在此,DAO层的设计首先是设计DAO的接口,然后在Spring的配置文件中定义此接口的实现类,然后就可在模块中调用此接口来进行数据业务的处理,而不用关心此接口的具体实现类是哪个类,显得结构非常清晰,DAO层的数据源配置,以及有关数据库连接的参数都在Spring的配置文件中进行配置. Service层:Service层主要负责业务模块的逻辑应用设计.同样是首先设计接口,再设计其实现的类,接着再Spring的配置文件中配

学习MVC之租房网站(四)-实现Service层并进行单元测试

在上一篇<学习MVC之租房网站(三)-编写Eneity类并创建数据库>中,记录了编写Eneity类并采用CodeFirst的方式创建数据库的过程,接下来就到了Service层的实现了,并且在开始后续工作前,首先进行充分的单元测试. 长久以来,一直为写出很多bug而苦恼,这儿用过单元测试后,惊喜地发现,这不正是保证代码质量的好方法嘛,虽然会耗费额外的时间,但决定以后要把单元测试运用到工作和学习的实践中. 一.实现Service层 1. 为了减少模块.层之间的耦合,在Service层上面增加了IS

高并发下的 Nginx 优化与负载均衡

高并发下的 Nginx 优化 英文原文:Optimizing Nginx for High Traffic Loads 过去谈过一些关于Nginx的常见问题; 其中有一些是关于如何优化Nginx. 很多Nginx新用户是从Apache迁移过来的,因些他们过去常常调整配置和执行魔术操作来确保服务器高效运行. 有一些坏消息要告诉你, 你不能像Apache一样优化Nginx.它没有魔术配置来减半负载或是让PHP运行速度加快一倍. 高兴的是, Nginx已经优化的非常好了. 当你决定使用Nginx并用a