在做电商系统时,库存是一个非常严格的数据,根据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. 点击其它地方DIV隐藏

    $(document).bind('click',function(e){ var e = e || window.event; //浏览器兼容性 var elem = e.target || e.s ...

  2. Launcher 壁纸

    0.添加壁纸: 在给系统换默认的壁纸的时候,需要修改一些地方: 首先是默认的壁纸,这个是在framework中配置的,所以要修改framework中找到drawable-nodpi(这个文件夹中的内容 ...

  3. javascript获取浏览器窗口大小

    var w=window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth; var h= ...

  4. 安装 modelsim 10.4

    安装教程: http://jingyan.baidu.com/article/da1091fb30d880027849d63a.html 在安装的过程中出现cmd乱(即在输入页面上停留字符),还有吱吱 ...

  5. web安全之xss

    xss:跨站脚本攻击,攻击者,把一段恶意代码镶嵌到web页面,,用户浏览页面时,嵌入页面的恶意代码就会执行,从而到达攻击用户的目的. 重点在于脚本,javascript和actionscript    ...

  6. Dos学习笔记(1)dir命令

    这个命令是最常用的命令,就像linux的ls一样,同样他也有很多很多optionnal field供我们选择, 看了半天,觉得自己离盲打肯定还是有很大的差距的,现在只是想体验一下dos,或者说感受下这 ...

  7. 为windows应用程序提供托盘图标

    1>包含头文件 #include "Shellapi.h"   2>相关结构体和函数:     NOTIFYICONDATA     WINSHELLAPI BOOL ...

  8. Jenkins的配置(rpm red hat方式)

    Jenkins的配置文件位置 #### sudo chown -R admin /usr/lib/jenkins sudo chgrp -R admin /usr/lib/jenkins #### s ...

  9. jquery的checkbox 全选和全不选

    今天写了一个checkbox的全选和全不选的功能: var check_all=function(){ if(this.checked){ //alert($(".adv_check_num ...

  10. c# 映射对比测试

    c#  映射对比测试(测试对象,测试案例,测试结果) 测试组件对象: TinyMapper-EmitMapper-AutoMapper-NLiteMapper-Handwritten 对比测试案例: ...