在做电商系统时,库存是一个非常严格的数据,根据CAS(check and swap)原来下面对库存扣减提供两种方法,一种是redis,一种用java实现CAS。

第一种 redis实现:

以下这个类是工具类,稍作修改就可运行

import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.springframework.beans.factory.support.BeanDefinitionReader;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.xml.XmlBeanDefinitionReader;

import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisCommands;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.util.Pool;

import com.maowu.commons.conf.FileUpdate;
import com.maowu.commons.conf.NotifyFileUpdate;
import com.maowu.commons.logutil.LogProxy;
import com.maowu.commons.util.LogicUtil;

public class JedisPoolFactory
{
    private static Logger log = LogProxy.getLogger(JedisPoolFactory.class);

private static String configFile = "jedisconf";
    /**
     * 主服务器数据源连接池,主要用于写操作
     */
    private static JedisPool jedisPool = null;

/**
     * 数据源共享连接池,主要用于读取数据
     */
    private static ShardedJedisPool shardedJedisPool = null;

static
    {
        loadXmlConfig();

NotifyFileUpdate.registInterface(new FileUpdate()
        {
            @Override
            public void updateFile(String fileName)
            {
                if (fileName.startsWith(configFile))
                {
                    log.debug("updateFile = " + fileName);
                    loadXmlConfig();
                }
            }
        });
    }

/**
     * @author: smartlv
     * @date: 2014年2月17日下午3:36:30
     */
    private static void loadXmlConfig()
    {
        DefaultListableBeanFactory context = new DefaultListableBeanFactory();
        BeanDefinitionReader reader = new XmlBeanDefinitionReader(context);
        reader.loadBeanDefinitions("classpath:autoconf/jedisconf.xml");

initJedisPool(context);
        initShardedJedisPool(context);
    }

private static void initJedisPool(DefaultListableBeanFactory context)
    {
        JedisConf conf = (JedisConf) context.getBean("jedisConf");

JedisShardInfo jsInfo = null;
        if (LogicUtil.isNullOrEmpty(conf.getJsInfo()))
        {
            return;
        }
        jsInfo = conf.getJsInfo().get(0);

jedisPool = new JedisPool(conf.getPoolConfig(), jsInfo.getHost(), jsInfo.getPort(), jsInfo.getTimeout(),
                jsInfo.getPassword());
    }

private static void initShardedJedisPool(DefaultListableBeanFactory context)
    {
        JedisConf conf = (JedisConf) context.getBean("shardedJedisConf");

shardedJedisPool = new ShardedJedisPool(conf.getPoolConfig(), conf.getJsInfo(), conf.getAlgo(),
                Pattern.compile(conf.getPattern()));
    }

public static JedisPool getJedisPool()
    {
        return jedisPool;
    }

public static ShardedJedisPool getShardedJedisPool()
    {
        return shardedJedisPool;
    }

/**
     * 打开一个普通jedis数据库连接
     *
     * @param jedis
     */
    public static Jedis openJedis() throws Exception
    {
        if (LogicUtil.isNull(jedisPool))
        {
            return null;
        }

return jedisPool.getResource();
    }

/**
     * 打开一个分布式jedis从数据库连接
     *
     * @throws
     * @author: yong
     * @date: 2013-8-30下午08:41:23
     */
    public static ShardedJedis openShareJedis() throws Exception
    {
        if (LogicUtil.isNull(shardedJedisPool))
        {
            return null;
        }

return shardedJedisPool.getResource();
    }

/**
     * 归还普通或分布式jedis数据库连接(返回连接池)
     *
     * @param p
     *        连接池
     * @param jedis
     *        客户端
     */

@SuppressWarnings({ "rawtypes", "unchecked" })
    public static void returnJedisCommands(Pool p, JedisCommands jedis)
    {
        if (LogicUtil.isNull(p) || LogicUtil.isNull(jedis))
        {
            return;
        }

try
        {
            p.returnResource(jedis);// 返回连接池
        }
        catch (Exception e)
        {
            log.error("return Jedis or SharedJedis to pool error", e);
        }
    }

/**
     * 释放redis对象
     *
     * @param p
     *        连接池
     * @param jedis
     *        redis连接客户端
     * @throws
     * @author: yong
     * @date: 2013-8-31下午02:12:21
     */
    @SuppressWarnings({ "rawtypes", "unchecked" })
    public static void returnBrokenJedisCommands(Pool p, JedisCommands jedis)
    {
        if (LogicUtil.isNull(p) || LogicUtil.isNull(jedis))
        {
            return;
        }

try
        {
            p.returnBrokenResource(jedis); // 获取连接,使用命令时,当出现异常时要销毁对象
        }
        catch (Exception e)
        {
            log.error(e.getMessage(), e);
        }
    }

}

redis 保证CAS的一个方法:

@Override
    public long decrLastActiSkuCount(String actiId, String skuId, long decrCount)
    {
        int NOT_ENOUGH = -2;// 库存不足
        long lastCount = -1;// 剩余库存
        int maxTryTime = 100;// 最大重试次数

Jedis jedis = null;
        JedisPool pool = JedisPoolFactory.getJedisPool();
        try
        {
            jedis = pool.getResource();

String key = actiId + Constants.COLON + skuId;
            byte[] bkey = SerializeUtil.serialize(key);
            LogUtil.bizDebug(log, "decr sku count key=[%s]", key);

for (int i = 0; i < maxTryTime; i++)
            {
                try
                {
                    jedis.watch(bkey);// 添加key监视
                    byte[] r = jedis.get(bkey);
                    long c = Long.valueOf(new String(r, Protocol.CHARSET));
                    if (c < decrCount)// 判断库存不充足
                    {
                        jedis.unwatch();// 移除key监视
                        return NOT_ENOUGH;// 库存不足
                    }
                    Transaction t = jedis.multi();// 开启事务,事务中不能有查询的返回值操作
                    t.decrBy(bkey, decrCount);// 修改库存数,该函数不能立即返回值
                    List<Object> trs = t.exec();// 事务执行结果
                    LogUtil.bizDebug(log, "transaction exec result=[%s]", trs);
                    if (LogicUtil.isNotNullAndEmpty(trs))
                    {
                        lastCount = (Long) trs.get(0);// 剩余库存
                        break;// 在多线程环境下,一次可能获取不到库存,当提前获取到库存时,跳出循环
                    }
                }
                catch (Exception e)
                {
                    log.error("watched key's value has changed", e);
                }

if (i == maxTryTime - 1)
                {
                    log.error("arrived max try time:" + maxTryTime);
                }
            }
        }
        catch (Exception e)
        {
            log.error("decr sku stock count error", e);
            JedisPoolFactory.returnBrokenJedisCommands(pool, jedis);
        }
        finally
        {
            JedisPoolFactory.returnJedisCommands(pool, jedis);
        }

return lastCount;
    }

第二种实现

数据类:

public class D
{
    public final static int INIT_VERSION = 0;
    volatile int c = 0;
    volatile int v = INIT_VERSION;

public synchronized int add(int c, int v)
    {
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        if (this.v == v)
        {
            this.c = this.c + c;
            this.v++;
            return this.c;
        }
        return -1;
    }

public int[] get()
    {
        try
        {
            Thread.sleep(1000);
        }
        catch (InterruptedException e)
        {
            e.printStackTrace();
        }
        return new int[] { c, v };
    }
}

访问类:访问库存

public class T implements Runnable
{
    D d;
    int i = 0;

public T(D d, int i)
    {
        this.d = d;
        this.i = i;
    }

public void changeStock(D d, int c)
    {
        for (int i = 0; i < 100; i++)
        {
            int g[] = d.get();
            if (g[0] < Math.abs(c))
            {
                System.out.println("库存不足");
                break;
            }
            else
            {
                int a = d.add(c, g[1]);
                if (a >= 0)
                {
                    System.out.println("待扣库存-->" + c + " 剩余库存-->" + a);
                    break;
                }
                else
                {
                    System.out.println("待扣库存-->" + c + " 版本号不对");
                }
            }
        }
    }

@Override
    public void run()
    {
        changeStock(d, i);
    }

public static void main(String[] args)
    {
        // 初始化库存
        D d = new D();
        int c = d.add(200, D.INIT_VERSION);
        System.out.println("---初始化" + c + "库存---");

Thread th[] = new Thread[10];
        for (int i = 0; i < th.length; i++)
        {
            th[i] = new Thread(new T(d, -i), "i=" + i);
        }

for (int i = 0; i < th.length; i++)
        {
            th[i].start();
        }
    }
}

我觉得第二种实现不能直接放入业务代码中,稍作修改应该可以。

自实现CAS原理JAVA版,模拟下单库存扣减的更多相关文章

  1. Java练习demo 20190402 优惠券扣减

    实体类: package org.jimmy.autosearch2019.pojo; import java.math.BigDecimal; public class TestEntity2019 ...

  2. java版模拟浏览器下载百度动漫图片到本地。

    package javaNet.Instance.ImageDownload; import java.io.BufferedReader; import java.io.File; import j ...

  3. J.U.C Atomic(一)CAS原理

    CAS概念 CAS:Compare And Swap,比较并交换.java.util.concurrent包完全是建立于CAS机制之上的. CAS原理 Java CAS是通过调用Unsafe的nati ...

  4. JAVA CAS原理深度分析-转载

    参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/reso ...

  5. JAVA CAS原理

    转自: http://blog.csdn.net/hsuxu/article/details/9467651 CAS CAS: Compare and Swap java.util.concurren ...

  6. 【转】JAVA CAS原理深度分析

    java.util.concurrent包完全建立在CAS之上的,没有CAS就不会有此包.可见CAS的重要性. CAS CAS:Compare and Swap, 翻译成比较并交换. java.uti ...

  7. JAVA CAS原理深度分析

    参考文档: http://www.blogjava.net/xylz/archive/2010/07/04/325206.html http://blog.hesey.net/2011/09/reso ...

  8. JAVA CAS原理深度分析(转)

    看了一堆文章,终于把JAVA CAS的原理深入分析清楚了. 感谢GOOGLE强大的搜索,借此挖苦下百度,依靠百度什么都学习不到! 参考文档: http://www.blogjava.net/xylz/ ...

  9. Java中的CAS原理

    前言:在对AQS框架进行分析的过程中发现了很多CAS操作,因此有必要对CAS进行一个梳理,也便更清楚的了解其原理. 1.CAS是什么 CAS,是compare and swap的缩写,中文含义:比较交 ...

随机推荐

  1. PHPnow在win8下安装失败的解决办法

    提示: 安装服务[ Apache_pn ]失败,可能原因如下:1.服务名已存在,请卸载或使用不同服务名.2.非管理员权限,不能操作Window NT服务. 解决方案: 搜索:命令提示符   , 右键以 ...

  2. Loadrunner 脚本错误问题汇总(非原创,部分转自互联网)

    在运行脚本回放过程中,有时会出现错误,这在实际测试中是不可避免的,毕竟自动录制生成的脚本难免会有问题,需要运行脚本进行验证,把问题都解决后才加入到场景中进行负载测试.下面结合常用的协议(如Web.We ...

  3. 从指定的URL下载文件

    通过使用URLDownLoadToFile函数,我们能从指定的URL下载文件,保存到本地,并且下载的文件类型可以是可执行文件 实例如下,http://www.xuexic.com 的根目录下存在一个l ...

  4. extjs组件添加事件监听的三种方式

    extjs对组件添加监听的三种方式  在定义组件的配置时设置 如代码中所示:  Java代码  xtype : 'textarea',  name : 'dataSetField',  labelSe ...

  5. 用python+selenium抓取知乎今日最热和本月最热的前三个问题及每个问题的首个回答并保存至html文件

    抓取知乎今日最热和本月最热的前三个问题及每个问题的首个回答,保存至html文件,该html文件的文件名应该是20160228_zhihu_today_hot.html,也就是日期+zhihu_toda ...

  6. CEPH经常出现slow request的排查解决

    现象: 通过ceph -w日志经常发现有request blocked的问题(如果虚拟机系统跑在ceph上时,就会发现严重的卡顿现象) 排查: 1.通过dstat未发现有明显的瓶颈 (dstat -t ...

  7. 最小生成树——kruskal算法

    kruskal和prim都是解决最小生成树问题,都是选取最小边,但kruskal是通过对所有边按从小到大的顺序排过一次序之后,配合并查集实现的.我们取出一条边,判断如果它的始点和终点属于同一棵树,那么 ...

  8. 转:Task任务调度实现生产者消费者模式 (个人理解后文)

    纯属个人愚见.欢迎加入反驳(PiDou). 1.前文大致就是,利用Queue配置的一个TaskFactory任务调度器.实现生产者消费者模式的例子..首先我就试了 第一种 FIFO(先进先出)的配置. ...

  9. 数据解析(XML和JSON数据结构)

    一   解析 二 XML数据结构 三 JSON 数据结构     一 解析 1  定义: 从事先规定好的格式中提取数据     解析的前提:提前约定好格式,数据提供方按照格式提供数据.数据获取方则按照 ...

  10. 编写中断例程7ch:计算word型数据的平方

    实现计算一个word型数据的平方. 这是安装程序. assume cs:code code segment start: mov ax, cs mov ds, ax mov si, offset sq ...