在做电商系统时,库存是一个非常严格的数据,根据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. Linux mint 18版本开启SSH服务

    linux mint 18版本默认是没有安装ssh server的 需要手动安装 安装ssh server: 此命令需要联网,会自动下载安装 安装之后看是否开始了ssh, 看到ssh-agent 和s ...

  2. Hql 中 dao 层 以及daoimpl 层的代码,让mvc 模式更直观简洁

    1.BaseDao接口: //使用BaseDao<T> 泛型 ,在service中注入的时候,只需要将T换为对应的bean即可 public interface BaseDao<T& ...

  3. 为sproto手写了一个python parser

    这是sproto系列文章的第三篇,可以参考前面的<为sproto添加python绑定>.<为python-sproto添加map支持>. sproto是云风设计的序列化协议,用 ...

  4. JSP基础总结(运行机制、脚本元素、指令元素、动作元素)

    JSP的运行机制: 1.转译阶段:JSP页面转换成Servlet类: 2.请求阶段:Servlet类执行,将相应结果发送至客户端. 流程解释: 1.用户访问某个JSP页面 2.服务器找到相应的JSP页 ...

  5. Java设计模式(三) 抽象工厂模式

    原创文章,同步发自作者个人博客,转载请注明出处 http://www.jasongj.com/design_pattern/abstract_factory/ 抽象工厂模式解决的问题 上文<工厂 ...

  6. php session的理解与使用

    session学习参考链接:1.http://www.w3cschool.cn/php_sessions.html:2.http://php.net/manual/zh/book.session.ph ...

  7. oracle信息统计

    优化器统计范围: 表统计: --行数,块数,行平均长度:all_tables:NUM_ROWS,BLOCKS,AVG_ROW_LEN:列统计: --列中唯一值的数量(NDV),NULL值的数量,数据分 ...

  8. HTML5 地理位置定位(HTML5 Geolocation)原理及应用

    地理位置(Geolocation)是 HTML5 的重要特性之一,提供了确定用户位置的功能,借助这个特性能够开发基于位置信息的应用.今天这篇文章向大家介绍一下 HTML5 地理位置定位的基本原理及各个 ...

  9. [Beta] postmortem

    设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 解决网站前端的数据处理以及获取问题,定义的很清楚,对于典型用户也比较清晰,因为主要只有一个用户,所以对于 ...

  10. python 3 学习笔记(一)

    由于之前学过python2,因此今天就想记录下第一天学习python3过程中的遇到的不同和之前没有太掌握的基础知识. python2和python3的语法区别 print语句 在Python2里,pr ...