当前位置:   article > 正文

面试中又被问到Redis如何实现抢购,赶快代码实现一波吧!

redis题目抢答 java如何实现

作者:goodlook0123

https://blog.csdn.net/goodlook0123

面试常常遇到写一个抢购实例,或者讲讲抢购实现想法,然后总是讲不明白,因为目前工作没做抢购这一块儿。

但是这个想法今天终于搞明白了,其中也参照了一些大佬的做法。

这篇文章直接使用redis,其中注释也写的挺明白的,直接上代码:

junit测试类:

  1. Log log = LogFactory.getLog(getClass());
  2.     @Autowired  
  3.     private RedisTemplate<String, Object> redisTemplate;  
  4.     @Test
  5.     public void testSys() throws Exception{
  6.         log.info("开始");
  7.          MsService service = new MsService();
  8.          for (int i = 0; i < 10; i++) {
  9.              ThreadB threadA = new ThreadB(service, redisTemplate, "MSKEY");
  10.              threadA.start();
  11.              log.info("*******************结束");
  12.          }
  13.     }

threadB类:

  1. private MsService service;
  2.     private RedisTemplate<String,Object> redisTemplate;
  3.     private String key;
  4.     public ThreadB(MsService service,RedisTemplate<String,Object> redisTemplate,String key) {
  5.         this.service = service;
  6.         this.redisTemplate=redisTemplate;
  7.         this.key=key;
  8.     }
  9.     @Override
  10.     public void run() {
  11.         try {
  12.             service.seckill(redisTemplate, key);
  13.         } catch (InterruptedException e) {
  14.             // TODO Auto-generated catch block
  15.             e.printStackTrace();
  16.         } catch (Exception e) {
  17.             // TODO Auto-generated catch block
  18.             e.printStackTrace();
  19.         }
  20.     }

msService类:

  1. Log log = LogFactory.getLog(getClass());
  2.      /***
  3.      * 抢购代码
  4.      * @param redisTemplate
  5.      * @param key pronum 首先用客户端设置数量
  6.      * @return
  7.      * @throws InterruptedException 
  8.      */
  9.     public boolean seckill(RedisTemplate<String,Object> redisTemplate, String key) throws Exception {
  10.         RedisLock lock = new RedisLock(redisTemplate, key, 1000020000);
  11.         try {
  12.             if (lock.lock()) {
  13.                 // 需要加锁的代码
  14.                 String pronum=lock.get("pronum");
  15.                 //修改库存
  16.                 if(Integer.parseInt(pronum)-1>=0) {
  17.                     lock.set("pronum",String.valueOf(Integer.parseInt(pronum)-1));
  18.                     log.info("库存数量:"+pronum+"     成功!!!"+Thread.currentThread().getName());
  19.                 }else {
  20.                     log.info("已经被抢光了,请参与下轮抢购");
  21.                 }
  22.                 log.info("++++++++++++++++++++++++++++++++++++++参加了抢购");
  23.                 return true;
  24.             } 
  25.         } catch (InterruptedException e) {
  26.             e.printStackTrace();
  27.         } finally {
  28.             // 为了让分布式锁的算法更稳键些,持有锁的客户端在解锁之前应该再检查一次自己的锁是否已经超时,再去做DEL操作,因为可能客户端因为某个耗时的操作而挂起,
  29.             // 操作完的时候锁因为超时已经被别人获得,这时就不必解锁了。 ————这里没有做
  30.             lock.unlock();
  31.         }
  32.         return false;
  33.     }

redisLock类:

  1. public class RedisLock {
  2.     private static Logger logger = LoggerFactory.getLogger(RedisLock.class);
  3.     private RedisTemplate<String,Object> redisTemplate;
  4.     private static final int DEFAULT_ACQUIRY_RESOLUTION_MILLIS = 100;
  5.     /**
  6.      * Lock key path.
  7.      */
  8.     private String lockKey;
  9.     /**
  10.      * 锁超时时间,防止线程在入锁以后,无限的执行等待
  11.      */
  12.     private int expireMsecs = 60 * 1000;
  13.     /**
  14.      * 锁等待时间,防止线程饥饿
  15.      */
  16.     private int timeoutMsecs = 10 * 1000;
  17.     private volatile boolean locked = false;
  18.     /**
  19.      * Detailed constructor with default acquire timeout 10000 msecs and lock
  20.      * expiration of 60000 msecs.
  21.      *
  22.      * @param lockKey
  23.      *            lock key (ex. account:1, ...)
  24.      */
  25.     public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey) {
  26.         this.redisTemplate = redisTemplate;
  27.         this.lockKey = lockKey + "_lock";
  28.     }
  29.     /**
  30.      * Detailed constructor with default lock expiration of 60000 msecs.
  31.      *
  32.      */
  33.     public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs) {
  34.         this(redisTemplate, lockKey);
  35.         this.timeoutMsecs = timeoutMsecs;
  36.     }
  37.     /**
  38.      * Detailed constructor.
  39.      *
  40.      */
  41.     public RedisLock(RedisTemplate<String,Object> redisTemplate, String lockKey, int timeoutMsecs, int expireMsecs) {
  42.         this(redisTemplate, lockKey, timeoutMsecs);
  43.         this.expireMsecs = expireMsecs;
  44.     }
  45.     /**
  46.      * @return lock key
  47.      */
  48.     public String getLockKey() {
  49.         return lockKey;
  50.     }
  51.     public String get(final String key) {
  52.         Object obj = null;
  53.         try {
  54.             obj = redisTemplate.execute(new RedisCallback<Object>() {
  55.                 @Override
  56.                 public Object doInRedis(RedisConnection connection) throws DataAccessException {
  57.                     StringRedisSerializer serializer = new StringRedisSerializer();
  58.                     byte[] data = connection.get(serializer.serialize(key));
  59.                     connection.close();
  60.                     if (data == null) {
  61.                         return null;
  62.                     }
  63.                     return serializer.deserialize(data);
  64.                 }
  65.             });
  66.         } catch (Exception e) {
  67.             logger.error("get redis error, key : {}", key);
  68.         }
  69.         return obj != null ? obj.toString() : null;
  70.     }
  71.     public String set(final String key,final String value) {
  72.         Object obj = null;
  73.         try {
  74.             obj = redisTemplate.execute(new RedisCallback<Object>() {
  75.                 @Override
  76.                 public Object doInRedis(RedisConnection connection) throws DataAccessException {
  77.                     StringRedisSerializer serializer = new StringRedisSerializer();
  78.                     connection.set(serializer.serialize(key), serializer.serialize(value));
  79.                     return serializer;
  80.                 }
  81.             });
  82.         } catch (Exception e) {
  83.             logger.error("get redis error, key : {}", key);
  84.         }
  85.         return obj != null ? obj.toString() : null;
  86.     }
  87.     public boolean setNX(final String key, final String value) {
  88.         Object obj = null;
  89.         try {
  90.             obj = redisTemplate.execute(new RedisCallback<Object>() {
  91.                 @Override
  92.                 public Object doInRedis(RedisConnection connection) throws DataAccessException {
  93.                     StringRedisSerializer serializer = new StringRedisSerializer();
  94.                     Boolean success = connection.setNX(serializer.serialize(key), serializer.serialize(value));
  95.                     connection.close();
  96.                     return success;
  97.                 }
  98.             });
  99.         } catch (Exception e) {
  100.             logger.error("setNX redis error, key : {}", key);
  101.         }
  102.         return obj != null ? (Boolean) obj : false;
  103.     }
  104.     private String getSet(final String key, final String value) {
  105.         Object obj = null;
  106.         try {
  107.             obj = redisTemplate.execute(new RedisCallback<Object>() {
  108.                 @Override
  109.                 public Object doInRedis(RedisConnection connection) throws DataAccessException {
  110.                     StringRedisSerializer serializer = new StringRedisSerializer();
  111.                     byte[] ret = connection.getSet(serializer.serialize(key), serializer.serialize(value));
  112.                     connection.close();
  113.                     return serializer.deserialize(ret);
  114.                 }
  115.             });
  116.         } catch (Exception e) {
  117.             logger.error("setNX redis error, key : {}", key);
  118.         }
  119.         return obj != null ? (String) obj : null;
  120.     }
  121.     /**
  122.      * 获得 lock. 实现思路: 主要是使用了redis 的setnx命令,缓存了锁. reids缓存的key是锁的key,所有的共享,
  123.      * value是锁的到期时间(注意:这里把过期时间放在value了,没有时间上设置其超时时间) 执行过程:
  124.      * 1.通过setnx尝试设置某个key的值,成功(当前没有这个锁)则返回,成功获得锁
  125.      * 2.锁已经存在则获取锁的到期时间,和当前时间比较,超时的话,则设置新的值
  126.      *
  127.      * @return true if lock is acquired, false acquire timeouted
  128.      * @throws InterruptedException
  129.      *             in case of thread interruption
  130.      */
  131.     public synchronized boolean lock() throws InterruptedException {
  132.         int timeout = timeoutMsecs;
  133.         while (timeout >= 0) {
  134.             long expires = System.currentTimeMillis() + expireMsecs + 1;
  135.             String expiresStr = String.valueOf(expires); // 锁到期时间
  136.             if (this.setNX(lockKey, expiresStr)) {
  137.                 // lock acquired
  138.                 locked = true;
  139.                 return true;
  140.             }
  141.             String currentValueStr = this.get(lockKey); // redis里的时间
  142.             if (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
  143.                 // 判断是否为空,不为空的情况下,如果被其他线程设置了值,则第二个条件判断是过不去的
  144.                 // lock is expired
  145.                 String oldValueStr = this.getSet(lockKey, expiresStr);
  146.                 // 获取上一个锁到期时间,并设置现在的锁到期时间,
  147.                 // 只有一个线程才能获取上一个线上的设置时间,因为jedis.getSet是同步的
  148.                 if (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
  149.                     // 防止误删(覆盖,因为key是相同的)了他人的锁——这里达不到效果,这里值会被覆盖,但是因为什么相差了很少的时间,所以可以接受
  150.                     // [分布式的情况下]:如过这个时候,多个线程恰好都到了这里,但是只有一个线程的设置值和当前值相同,他才有权利获取锁
  151.                     // lock acquired
  152.                     locked = true;
  153.                     return true;
  154.                 }
  155.             }
  156.             timeout -= DEFAULT_ACQUIRY_RESOLUTION_MILLIS;
  157.             /*
  158.              * 延迟100 毫秒, 这里使用随机时间可能会好一点,可以防止饥饿进程的出现,即,当同时到达多个进程,
  159.              * 只会有一个进程获得锁,其他的都用同样的频率进行尝试,后面有来了一些进行,也以同样的频率申请锁,这将可能导致前面来的锁得不到满足.
  160.              * 使用随机的等待时间可以一定程度上保证公平性
  161.              */
  162.             Thread.sleep(DEFAULT_ACQUIRY_RESOLUTION_MILLIS);
  163.         }
  164.         return false;
  165.     }
  166.     /**
  167.      * Acqurired lock release.
  168.      */
  169.     public synchronized void unlock() {
  170.         if (locked) {
  171.             redisTemplate.delete(lockKey);
  172.             locked = false;
  173.         }
  174.     }

至此基于redis的抢购简单实现。大佬如果觉得有不妥的地方请指正,小弟也可以进步一点点。

Java面试题专栏

【71期】面试官:对并发熟悉吗?谈谈你对Java中常用的几种线程池的理解

【72期】面试官:对并发熟悉吗?说一下synchronized与Lock的区别与使用

【73期】面试官:Spring 和 Spring Boot 的区别是什么?

【74期】面试官:对多线程熟悉吗,来谈谈线程池的好处?

【75期】面试官:说说Redis的过期键删除策略吧!(高频)

【76期】面试官问:List如何一边遍历,一边删除?

【77期】这一道面试题就考验了你对Java的理解程度

【78期】别找了,Java集合面试问题这里帮你总结好了!

【79期】别找了,回答Spring中Bean的生命周期,这里帮你总结好了!

【80期】说出Java创建线程的三种方式及对比

欢迎长按下图关注公众号后端技术精选

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/很楠不爱3/article/detail/606223
推荐阅读
相关标签
  

闽ICP备14008679号