java吧 关注:1,236,779贴子:12,705,352
  • 9回复贴,共1

请教大神,关于高并发与乐观所的问题..

只看楼主收藏回复

废话不说了。直接贴代码
package com.test.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ConcurrentAsset {
private static Lock lock = new ReentrantLock();
public static void update(Asset asset,double newMoney) {
try{
//lock.lock();之所以注释掉,是发现问题,
//假设用户从界面查看的产品,在这个时候界面是有产品对应的乐观所版本号的.所以即使加锁,在并发的情况下,乐观所的问题还是存在
//查询资产目前价值
Double money = JDBCUtil.queryAsset().getMoney();
//比对资产剩余价值是否还可以减少,模拟,固定每次资产少于1千,总资产为1万
//这里其实说白了就类似于抢购,更新库存,多人并发操作数据库中的一条数据
if(money>=newMoney){
Object[] params = {newMoney,asset.getId(),asset.getVersion()};
//更新资产表(库存表,+了乐观所机制,每次更新版本递增+1),
StringBuilder sql = new StringBuilder();
sql.append("update asset set ");
sql.append("money=money-?,");
sql.append("version=version+1 ");
sql.append("where id = ? and version = ?");
int r = JDBCUtil.update(sql.toString(),params);
if (r == 0) {
//模拟环境,加入执行结果为0就抛出异常
throw new RuntimeException("乐观所异常");
}else{
JDBCUtil.commint();
}
}else{
throw new RuntimeException("余额不足");
}
}finally{
//释放JDBC相关..ThreadLocal..
JDBCUtil.release();
//lock.unlock();释放锁
}
}
public static void main(String[] args) {
//模拟并发操作,创建15个大小的线程池
ExecutorService executorService = Executors.newFixedThreadPool(15);
//模拟5个用户并发购买资产
for (int i = 0; i < 5; i++) {
//并发操作
executorService.execute(new Runnable() {
@Override
public void run() {
try{
//模拟页面客户刚刚检索出的数据,包含数据ID,和乐观所版本号
Asset asset = JDBCUtil.queryAsset();
update(asset, 1000);
}catch(Exception e){
System.err.println(e.getMessage());
}
}
});
}
//关闭线程池...
executorService.shutdown();
}
}
不知道大家伙能看明白我这段代码的意思么?
实际上,我是想说,怎样处理并发情况下,乐观所的异常。
场景案例,腾讯的红包,
n个人抢1份数量为10的红包,金额肯定是有限的。。
每抢一次,金额必然减少,数量肯也会减少。
假设内置了乐观所,在更新红包金额和数量的时候,
为了保证处理中数据的一致性,先从数据库读取数据对应的 乐观所版本号。
然后逻辑校验..可能中间调用了其他组件..经过一系列处理校验通过,更新数据,戴上版本号。但是发现版本号已经过时了..实际上红包的数量和金额还是可以抢的。
这个问题具体应该怎么解决呢?
哪位有经验的大神给讲讲?


IP属地:四川1楼2015-08-29 00:21回复
    @alwing
    @情儿沫沫
    @╱―★神★―╲
    大神召唤


    IP属地:四川2楼2015-08-29 00:25
    回复
      错别字!


      IP属地:浙江3楼2015-08-29 01:04
      收起回复
        (⊙o⊙)…,刚刚测试,去掉乐观所,打开lock.发现代码是没有问题的。。
        刚搜了搜相关资料,还是有带研究啊..貌似说是用队列解决。。欢迎探讨!


        IP属地:四川4楼2015-08-29 01:05
        回复
          mark坐等大神


          IP属地:北京来自Android客户端5楼2015-08-29 02:15
          回复
            如果用悲观锁,其实就不需要乐观锁了
            而且悲观锁跟乐观锁一般都是看实际场景选择的
            例如你这里的例子,抢红包的场景 人数可能并不会很多(上万人抢一个红包的情况多吗?)
            那么为了编写方便可能更多的是用悲观锁 当然 如果要更高的性能 可能就不适合了 按数据的重要程度 可能用CAS 或者队列的方式(实际情况是锁的方案更多,毕竟不是那么多系统都有很高的性能要求(或者说达到锁性能的极限),当然,锁性能满足不了的话,队列的方式还是比较科学的)
            首先是要明白 并发的是请求 但是对金额的操作 不应该是并发 换一种说法 对金额的操作 应该是原子性的
            那么如何保证金额的操作是原子性的呢? 其实常识已经告诉我们 按序的执行操作 肯定是没问题的(想想你去买东西 老板是怎么算钱的?
            悲观锁其实就是保证对金额的操作能顺序执行 队列也是这个道理
            关于为什么用悲观锁 而不是乐观锁,主要还是看业务场景。
            就说抢红包,用户执行了抢红包操作,只要红包里还有足够的金额,肯定是允许用户抢到的,而不是说提醒用户 “刚被人抢了,请刷新一下重新抢” 这种非常不科学的方式
            但是如果是对数据的操作,比如我修改一下taobao下单的信息,那么为了保证数据操作对用户是可见的,即用户看到A 改成B,而不是用户看到A,但是实际已经被他人改成了C,然后用户改成B 这种情况是糟糕的 但是还是那句话 看业务场景 其实很多地方都不需要乐观锁的...
            以上可能并不是技术方案 更多的是对并发操作与处理方案的思考
            好好想想吧


            IP属地:北京6楼2015-08-29 02:44
            收起回复
              单机就原子类,集群就zk


              IP属地:辽宁来自iPhone客户端7楼2015-08-29 19:53
              回复