数据库:mysql

数据库的乐观锁:一般通过数据表加version来实现,相对于悲观锁的话,更能省数据库性能,废话不多说,直接看代码

第一步:

建立数据库表:

 

 CREATE TABLE `skill_activity` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '活动id',
`name` varchar(20) NOT NULL COMMENT '活动名称',
`num` bigint(10) NOT NULL COMMENT '活动数量限制',
`surplus_num` bigint(10) NOT NULL COMMENT '活动剩余数量',
`person_limit` bigint(10) NOT NULL COMMENT '单人上传限制',
`version` bigint(10) DEFAULT '0',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8; CREATE TABLE `skill_activity_order` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`activity_id` bigint(20) NOT NULL COMMENT '活动id',
`thread_id` bigint(20) NOT NULL COMMENT '线程id',
`create_at` datetime DEFAULT NULL COMMENT '创建时间',
`name` varchar(20) NOT NULL COMMENT '活动名称',
`url` varchar(20) DEFAULT NULL, PRIMARY KEY (`id`),
KEY `index_thread_id` (`thread_id`) ) ENGINE=InnoDB AUTO_INCREMENT=106338 DEFAULT CHARSET=utf8

往数据库活动表(skill_activity)插入一条数据

num:商品数量;surplus_num:商品剩余数量;person_limit:单人上传数量限制;version:版本号,解决高并发问题

具体的活动秒杀订单表(skill_activity_order):

activity_id:就是上面活动表的id;thread_id:就是线程id,实际秒杀就是用户id,name和url就是秒杀填写的一些内容,不必关注

第二步:

java代码:

1)2个表对应的mapper.java类

public interface SkillActivityMapper {
int deleteByPrimaryKey(Long id); int insert(SkillActivity record); int insertSelective(SkillActivity record); SkillActivity selectByPrimaryKey(Long id); int updateByPrimaryKeySelective(SkillActivity record); int updateByPrimaryKey(SkillActivity record); int updateSkillActivityNum(SkillActivity record);//秒杀修改剩余数量的方法
}
public interface SkillActivityOrderMapper {
int deleteByPrimaryKey(Long id); int insert(SkillActivityOrder record); int insertSelective(SkillActivityOrder record); SkillActivityOrder selectByPrimaryKey(Long id); List<SkillActivityOrder> selectBythreadId(Long threadId); int updateByPrimaryKeySelective(SkillActivityOrder record); int updateByPrimaryKey(SkillActivityOrder record);
}

  

2)2个类对应的mapper.xml方法就不一一写出来了,看SkillActivityMapper的updateSkillActivityNum这个方法的sql语句:

<update id="updateSkillActivityNum" parameterType="com.ouer.model.SkillActivity" >
update skill_activity
set
surplus_num = #{surplusNum,jdbcType=BIGINT},
version = #{version,jdbcType=BIGINT}+1
where id = #{id,jdbcType=BIGINT} and version=#{version,jdbcType=BIGINT} and surplus_num>0
</update>

还有SkillActivityOrderMapper.selectBythreadId方法

<select id="selectBythreadId" resultMap="BaseResultMap"  >
select
<include refid="Base_Column_List" />
from skill_activity_order
where thread_id = #{threadId,jdbcType=BIGINT}
</select>

3)version版本号是关键,update成功会导致version加1,而其他线程如果是原先的version就无法update。

4)看一下service代码:

@Override
public SkillActivityResponse SkillActivity(SkillActivirtReq req) {
SkillActivityResponse skillActivityResponse=new SkillActivityResponse();
int failNum=0;
SkillActivity skillActivity=skillActivityMapper.selectByPrimaryKey(req.getActivityId());
List<String> urls=req.getUrls();
if(skillActivity.getSurplusNum()<=0){
skillActivityResponse.setErrorMsg("活动已经结束");
skillActivityResponse.setFailNum(urls.size());
skillActivityResponse.setSucceed(false);
return skillActivityResponse;
}else{
//先查询用户上传了多少张
int count=skillActivityOrderMapper.selectBythreadId(req.getThreadId()).size();//查询每个用户上传了多少张
if(count>skillActivity.getPersonLimit()){
skillActivityResponse.setErrorMsg("已经超出上传上限,上传失败");
skillActivityResponse.setFailNum(urls.size());
skillActivityResponse.setSucceed(false);
return skillActivityResponse;
} int index=(int) (skillActivity.getPersonLimit()-count);//表示还能上传的数量
if(urls.size()<=index){
//都可以上传
}else{
//表示只能上传index张图片
urls=urls.subList(0, index);
} //上传订单
for(int i=0;i<urls.size();i++){
skillActivity= skillActivityMapper.selectByPrimaryKey(req.getActivityId());
skillActivity.setSurplusNum(skillActivity.getSurplusNum()-1);
if(skillActivity.getSurplusNum()<0){
failNum++;
continue;
}
int result=skillActivityMapper.updateSkillActivityNum(skillActivity);//这个是关键
if(result>0){
//上传成功
SkillActivityOrder activityOrder=new SkillActivityOrder();
activityOrder.setActivityId(skillActivity.getId());
activityOrder.setCreateAt(new Date());
activityOrder.setName(skillActivity.getName());
activityOrder.setThreadId(req.getThreadId());
activityOrder.setUrl(urls.get(i));
skillActivityOrderMapper.insertSelective(activityOrder);
}else{
//上传失败
failNum++;
}
} skillActivityResponse.setFailNum(failNum);
skillActivityResponse.setSucceed(true);
return skillActivityResponse;
} }

5)使用spring的junit来单元测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:spring/applicationContext.xml"})
public class SkillActivityServieTest {
@Autowired
private SkillActivityService skillActivityService; class MyRun implements Runnable{
private CyclicBarrier barrier; private CountDownLatch countDownLatch; private Long threadId; public MyRun(CyclicBarrier barrier, CountDownLatch countDownLatch,
Long threadId) {
super();
this.barrier = barrier;
this.countDownLatch = countDownLatch;
this.threadId = threadId;
} @Override
public void run() {
System.err.println("线程"+threadId+"准备完毕");
try {
barrier.await();
SkillActivirtReq req=new SkillActivirtReq();
req.setActivityId(1L);
req.setThreadId(threadId);
req.setUrls(Lists.newArrayList("url1","url2","url3","url4","url5","url6","url7"
,"url7","url8","url9","url10"));
SkillActivityResponse skillActivityResponse= skillActivityService.SkillActivity(req);
if(skillActivityResponse.isSucceed()){
System.err.println("线程:"+threadId+",failNum:"+skillActivityResponse.getFailNum());
}else{
System.err.println("线程:"+threadId+",errmsg:"+skillActivityResponse.getErrorMsg());
}
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally{
countDownLatch.countDown();
} } } @Test
public void test01() throws InterruptedException{
CyclicBarrier barrier = new CyclicBarrier(20000);// 让20000个线程同时进行操作 调用20000次方法await() 才会让20000个线程同时执行
CountDownLatch countDownLatch=new CountDownLatch(20000);//统计耗时
ExecutorService executorService= Executors.newCachedThreadPool();
long start=System.currentTimeMillis();
for(int i=1;i<=20000;i++){
executorService.submit(new MyRun(barrier, countDownLatch, new Long(i+"")));
}
executorService.shutdown();
countDownLatch.await();
System.err.println("耗时:"+(System.currentTimeMillis()-start)+"ms");
try {
Thread.sleep(Integer.MAX_VALUE); //防止线程结束
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

6)运行junit

会看见20000线程同时准备完毕后才会同时去秒杀商品,这个就是CyclicBarrier的作用

然后可以看见耗时:160945ms也就是160秒多一点

7)看看数据库表:

surplus_num的数量:0,version:2000,在多运行就几次也是这个结果,通过使用数据库的乐观锁来实现高并发下的秒杀

总结:数据库的乐观锁一般使用version版本号结合业务来实现,CyclicBarrier和CountDownLatch也是高并发下常用的工具类,CyclicBarrier的作用:就是让多个线程同时去操作,

CountDownLatch一般可以用来统计总耗时,由于作者水平有限,如有不足请见谅.

使用数据库乐观锁解决高并发秒杀问题,以及如何模拟高并发的场景,CyclicBarrier和CountDownLatch类的用法的更多相关文章

  1. 使用mysql乐观锁解决并发问题

    案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后提交.最后实际账户余额为1000 ...

  2. 使用mysql乐观锁解决并发问题思路

    本文摘自网络,仅供个人学习之用 案例说明: 银行两操作员同时操作同一账户.比如A.B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户扣除50元,A先提交,B后 ...

  3. 使用MySQL乐观锁解决超卖问题

    在秒杀系统设计中,超卖是一个经典.常见的问题,任何商品都会有数量上限,如何避免成功下订单买到商品的人数不超过商品数量的上限,这是每个抢购活动都要面临的难点. 1 超卖问题描述 在多个用户同时发起对同一 ...

  4. 数据库乐观锁和悲观锁的理解和实现(转载&总结)

    数据的锁定分为两种,第一种叫作悲观锁,第二种叫作乐观锁. 1.悲观锁,就是对数据的冲突采取一种悲观的态度,也就是说假设数据肯定会冲突,所以在数据开始读取的时候就把数据锁定住.[数据锁定:数据将暂时不会 ...

  5. web开发中的两把锁之数据库锁:(高并发--乐观锁、悲观锁)

    这篇文章讲了 1.同步异步概念(消去很多疑惑),同步就是一件事一件事的做:sychronized就是保证线程一个一个的执行. 2.我们需要明白,锁机制有两个层面,一种是代码层次上的,如Java中的同步 ...

  6. 简论数据库乐观悲观锁与并发编程中的CAS

    为了防止无良网站的爬虫抓取文章,特此标识,转载请注明文章出处.LaplaceDemon/ShiJiaqi. http://www.cnblogs.com/shijiaqi1066/p/5783205. ...

  7. 使用mysql悲观锁解决并发问题

    最近学习了一下数据库的悲观锁和乐观锁,根据自己的理解和网上参考资料总结如下: 悲观锁介绍(百科): 悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持 ...

  8. Java并发问题--乐观锁与悲观锁以及乐观锁的一种实现方式-CAS

    首先介绍一些乐观锁与悲观锁: 悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁.传统的关系型数据库里边就用到了很 ...

  9. Hibernate事务与并发问题处理(乐观锁与悲观锁)

    目录 一.数据库事务的定义 二.数据库事务并发可能带来的问题 三.数据库事务隔离级别 四.使用Hibernate设置数据库隔离级别 五.使用悲观锁解决事务并发问题 六.使用乐观锁解决事务并发问题 Hi ...

随机推荐

  1. 大数据hadoop面试题2018年最新版(美团)

    还在用着以前的大数据Hadoop面试题去美团面试吗?互联网发展迅速的今天,如果不及时更新自己的技术库那如何才能在众多的竞争者中脱颖而出呢? 奉行着"吃喝玩乐全都有"和"美 ...

  2. 从零部署Spring boot项目到云服务器(准备工作)

    自己的博客终于成功部署上线了,回过头来总结记录一下整个项目的部署过程! 测试地址:47.94.154.205:8084 注:文末有福利! 一.Linux下应用Shell通过SSH连接云服务器 //ss ...

  3. 福州大学W班-助教总结

    开学初对自己的期望 在即将到来的学期前,我希望我可以做到以下几点: 1.多参与同学的课程设计,并提出自己的见解 2.不断提高个人的专业技能,活到老学到老 3.能够及时对同学的博客进行评论,并给出有用的 ...

  4. 【alpha冲刺】随笔合集

    Daily Scrum Meeting 第一天 [Alpha]Daily Scrum Meeting第一次 第二天 [Alpha]Daily Scrum Meeting第二次 第三天 [Alpha]D ...

  5. Alpha冲刺Day6

    Alpha冲刺Day6 一:站立式会议 今日安排: 由张梨贤继续完成前一天委托第三方剩余的内容,并完成委托情况查看这一子模块 由黄腾飞继续完成前一天企业自查风险管理剩余的内容,并完成风险上报这一子模块 ...

  6. collections deque队列及其他队列

    from collections import deque dq = deque(range(10),maxlen=10) dq.rotate(3)#队列旋转操作接受一个参数N,让N>0时,队列 ...

  7. New UWP Community Toolkit - AdaptiveGridView

    概述 UWP Community Toolkit  中有一个自适应的 GridView 控件 - AdaptiveGridView,本篇我们结合代码详细讲解  AdaptiveGridView 的实现 ...

  8. CentOS 7 PHP-redis扩展安装,浏览器不显示数据及redis无法储存数据常见问题解决办法

    首先使用php -m 可以查看到自己安装了那些扩展. 1.使用wget下载redis压缩包 wget https://github.com/phpredis/phpredis/archive/deve ...

  9. iot:下一步要做的工作

    1.DeviceMessage抽象(定义&支持扩展)2.createDeviceMessage.analyseDeviceMessage(支持扩展)3.日志打印4.错误处理5.断线重连6.交互 ...

  10. SpringCloud应用入库后乱码问题

    一.现象 1.请求 2.入库后 二.解决过程 1.配置application.properties 2.代码配置 3.数据库(关键!!) 3.请求 三.验证过程 1.win10 - 本地验证通过 2. ...