# 简介:

当高并发访问某个接口的时候,如果这个接口访问的数据库中的资源,并且你的数据库事务级别是可重复读(Repeatable read)的话,确实是没有线程问题的,因为数据库锁的级别就够了;但是如果这个接口需要访问一个静态变量、静态代码块、全局缓存的中的资源或者redis中的资源的时候,就会出现线程安全的问题。

## 案例:

**github地址:** https://github.com/mzd123/mywy/tree/master/src/main/java/com/mzd/mywy/service

```

@RestController

public class MsController {

@Autowired

private MsService msService;

@RequestMapping("/select_info.do")

public String select_info(String product_id) {

return msService.select_info(product_id);

}

@RequestMapping("/order.do")

public String order(String product_id) throws CongestionException {

return msService.order1(product_id);

}

}

```

```

@Service

public class MsService {

@Autowired

private RedisLock redisLock;

//商品详情

private static HashMap product = new HashMap();

//订单表

private static HashMap orders = new HashMap();

//库存表

private static HashMap stock = new HashMap();

static {

product.put("123", 10000);

stock.put("123", 10000);

}

public String select_info(String product_id) {

return "限量抢购商品XXX共" + product.get(product_id) + ",现在成功下单" + orders.size()

+ ",剩余库存" + stock.get(product_id) + "件";

}

/**

* 下单

*

* @param product_id

* @return

*/

public String order1(String product_id) {

if (stock.get(product_id) == 0) {

return "活动已经结束了";

//已近买完了

} else {

//还没有卖完

try {

//模拟操作数据库

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

return select_info(product_id);

}

}

```

如上图所述,我现在需要限购10000个商品id为123的商品,如果什么操作也不做,直接访问静态资源你们觉得会有问题吗?我们使用apache_ab来模拟一下高并发情况,下面是发起100个请,并发量是50的情况:

![在这里插入图片描述](https://img-blog.csdn.net/20180918091230687?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**问题:** 可以看到,下单数和库存加起来明显超过了商品总数,这是一种超卖现象,在java角度来说就是线程不安全现象。

**解决1:** 学过javase的小伙伴应该都能想到使用synchronized关键字,强行同步。

```

/**

* 下单

*

* @param product_id

* @return

*/

public synchronized String order2(String product_id) {

if (stock.get(product_id) == 0) {

return "活动已经结束了";

//已近买完了

} else {

//还没有卖完

try {

//模拟操作数据库

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

return select_info(product_id);

}

```

![在这里插入图片描述](https://img-blog.csdn.net/20180918094822673?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**缺点:**

1、我们可以明显的看到速度变慢了,从原来的0.535秒变到了10.956秒,那是因为synchronized放这个方法只允许单线程访问了。

2、synchronized是粗粒度的控制了线程安全,即:如果我这个商品id不一样的线程,理论上是可以同时访问这个方法的,但是加上了synchronized之后,无论商品id是否一样,两个线程都是没法同时访问这个方法的。

**解决2:** 使用redis分布式锁(主要使用了redis中的setnx和getset方法,这两个方法在redisTemplate分别是setIfAbsent和getAndSet方法)实现线程安全,因为redis是单线程,能保证线程的安全性,而且redis强大的读写能力能提高效率。

```

/**

* 高并发没问题,效率还行

*

* @param product_id

* @return

*/

public String order3(String product_id) throws CongestionException {

/**

* redis加锁

*/

String value = System.currentTimeMillis() + 10000 + "";

if (!redisLock.lock1(product_id, value)) {

//系统繁忙,请稍后再试

throw new CongestionException();

}

//##############################业务逻辑#################################//

if (stock.get(product_id) == 0) {

return "活动已经结束了";

//已近买完了

} else {

//还没有卖完

try {

//模拟操作数据库

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

orders.put(MyStringUtils.getuuid(), product_id);

stock.put(product_id, stock.get(product_id) - 1);

}

//##############################业务逻辑#################################//

/**

* redis解锁

*/

redisLock.unlock(product_id, value);

return select_info(product_id);

}

```

```

/**

* 用redis实现分布式锁

*/

@Component

public class RedisLock {

@Autowired

private StringRedisTemplate redisTemplate;

//加锁

public boolean lock1(String key, String value) {

//setIfAbsent相当于jedis中的setnx,如果能赋值就返回true,如果已经有值了,就返回false

//即:在判断这个key是不是第一次进入这个方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:这个key还没有被赋值的时候

return true;

}

return false;

}

//解锁

public void unlock(String key, String value) {

try {

if (MyStringUtils.Object2String(redisTemplate.opsForValue().get(key)).equals(value)) {

redisTemplate.opsForValue().getOperations().delete(key);

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

```

**缺点:** 这个方法看上去没什么问题,而且只有商品id相同的两个线程同时访问这个方法的时候才会出现线程问题,这似乎是很完美了。但是有没有想过,万一处理业务逻辑的代码块中出现了异常,直接抛了出去,那解锁的代码就再也不会被执行了,也就是出现了死锁现象。

**改进1:**

```

public boolean lock2(String key, String value) {

//setIfAbsent相当于jedis中的setnx,如果能赋值就返回true,如果已经有值了,就返回false

//即:在判断这个key是不是第一次进入这个方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:这个key还没有被赋值的时候

return true;

}

String current_value = redisTemplate.opsForValue().get(key);//①

if (!MyStringUtils.Object2String(current_value).equals("")

//超时了

&& Long.parseLong(current_value) < System.currentTimeMillis()) {;//②

//返回true就能解决死锁

return true;

}

return false;

}

```

**缺点:** 使用超时时间来解决死锁问题,但是又出现新的问题,就是当有两个商品id相同的线程同时执行到了②这一行代码,这时候两个线程同时获取锁,这样一来任然存在线程安全问题了。。。

**改进2:**

```

/**

* 加锁

*/

public boolean lock3(String key, String value) {

//setIfAbsent相当于jedis中的setnx,如果能赋值就返回true,如果已经有值了,就返回false

//即:在判断这个key是不是第一次进入这个方法

if (redisTemplate.opsForValue().setIfAbsent(key, value)) {

//第一次,即:这个key还没有被赋值的时候

return true;

}

String current_value = redisTemplate.opsForValue().get(key);

if (!MyStringUtils.Object2String(current_value).equals("")

//超时了

&& Long.parseLong(current_value) < System.currentTimeMillis()) {//①

String old_value = redisTemplate.opsForValue().getAndSet(key, value);//②

if (!MyStringUtils.Object2String(old_value).equals("")

&& old_value.equals(current_value)) {

return true;

}

}

return false;

}

```

**解释:** 如果两个线程同时调用这个方法,当同时走到①的时候,无论怎么样都有一个线程会先执行②这一行,假设线程1先执行②这行代码,那redis中key对应的value就变成了value,然后线程2再执行②这行代码的时候,获取到的old_value就是value,那么value显然和他上面获取的current_value是不一样的,则线程2是没法获取锁的。

![在这里插入图片描述](https://img-blog.csdn.net/20180918105344515?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

![在这里插入图片描述](https://img-blog.csdn.net/20180918105442595?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3R1ZXNkYXltYQ==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70)

**说明:** 虽然100个请求只有2个成功下单的,但是耗时却明显变小了,而且线程也是安全的,只是绝大部分因为没有拿到锁而没有抢到限购的商品,但也做了人性化的提醒,个人觉得还是可以接受的!

java实现分布式redis锁_使用redis实现分布式锁相关推荐

  1. 关抢占 自旋锁_关于Redis分布式锁这一篇应该是讲的最好的了,先收藏起来再看!...

    作者:Nan,气冲天. 原文:https://blog.csdn.net/qq_44209336 前言 在Java并发编程中,我们通常使用到synchronized .Lock这两个线程锁,Java中 ...

  2. zookeeper 分布式锁_关于redis分布式锁,zookeeper分布式锁原理的一些学习与思考

    编辑:业余草来源:https://www.xttblog.com/?p=4946 首先分布式锁和我们平常讲到的锁原理基本一样,目的就是确保,在多个线程并发时,只有一个线程在同一刻操作这个业务或者说方法 ...

  3. 删除sybase里面的锁_一起来学习分布式锁

    为什么要用分布式锁 我们先来看一个业务场景: 系统 A 是一个电商系统,目前是一台机器部署,系统中有一个用户下订单的接口,但是用户下订单之前一定要去检查一下库存,确保库存 足够了才会给用户下单. 由于 ...

  4. redis数据类型_认识Redis与Redis的数据类型

    本文作为Redis的入门教程,旨在让大家对Redis有一个概念性和整体性的认识,并且可以快速上手,为深入Redis打下基础. 文章概要: 1. Redis的介绍 2. Redis与其他数据库的对比 3 ...

  5. mysql 查看锁_别吵吵,分布式锁也是锁

    Tomcat是这个系统的核心组成部分, 每当有用户请求过来,Tomcat就会从线程池里找个线程来处理,有的执行登录,有的查看购物车,有的下订单,看着属下们尽心尽职地工作,完成人类的请求,Tomcat就 ...

  6. 正在等待缓存锁:无法获得锁_一句话说清分布式锁,进程锁,线程锁

    推荐阅读 1. Java 性能优化:教你提高代码运行的效率 2. Java问题排查工具清单 3. 记住:永远不要在MySQL中使用UTF-8 4. Springboot启动原理解析 在分布式集群系统的 ...

  7. 请列举你了解的分布式锁_终于搞懂分布式锁是什么了!

    当下在互联网技术架构中,最流行的莫过于分布式架构了.为什么大家纷纷都采用分布式架构呢? 1.高效低廉,将部署在高性能机的程序分散在多个小型机中部署: 2.扩展性强,可随着业务的扩展而横向扩展系统的性能 ...

  8. php redis 投票_高性能Redis服务架构分析与搭建

    基于内存的Redis应该是目前各种web开发业务中最为常用的key-value数据库了,我们经常在业务中用其存储用户登陆态(Session存储),加速一些热数据的查询(相比较mysql而言,速度有数量 ...

  9. 为什么我的mysql比redis快_为什么redis是单线程的以及为什么这么快?

    官网的说法 我们先来认真看一下官网的说法.翻译过来大意如下: CPU并不是您使用Redis的瓶颈,因为通常Redis要么受内存限制,要么受网络限制.例如,使用在一般Linux系统上运行的流水线Redi ...

  10. python安装redis模块_安装redis及python redis模块

    第一部分:安装redis mkdir /usr/local/redis cd /usr/local/src wget http://download.redis.io/releases/redis-2 ...

最新文章

  1. Intellij idea generate builder 插件-用于自动生成builder模式代码
  2. Unity_UIWidgets学习笔记03_组件_Container
  3. CString .Format
  4. 为什么一个java源文件中只能有一个public类
  5. Kappa信息化谋局电子商务与传统渠道的全面管理
  6. 读书笔记_打开量化投资的黑箱09
  7. js调用html文件上传,JavaScript里的文件上传API
  8. 版式设计——网页排版
  9. python实现pdf转ppt_wps中pdf转成word文档 Python转换PPT为PDF
  10. 补码中关于-32768的问题
  11. 火车票软件哪个好用_买火车票哪个软件好用 哪个软件买火车票便宜
  12. Android 屏蔽Power键 Home键
  13. 关于运行项目时 vue-pdf 插件依赖报错的问题及解决办法
  14. Leetcode(24)——两两交换链表中的节点
  15. 基本软件开发模型:瀑布模型、V型模型、迭代模型、增量模型、螺旋模型、大爆炸模型、敏捷模型、原型模型、W模型 特点分析与总结
  16. hbuilder 上传图片 保存图片 拍照 muse ui
  17. git remote add origin xxx.git 的问题解决
  18. Swiper轮播图插件之如何修改前进后退按钮swiper-button-prev和swiper-button-next的默认样式
  19. Android KTX与Kotlin Android Extensions
  20. 新手入门手把手教你自学吉他,简单易懂看完就会

热门文章

  1. velocity参数重新赋值_Velocity(5)——#set指令
  2. 目前最常用的计算机机箱类型为_绍兴承接离心风机箱高品质
  3. 微型计算机字,微型计算机杂志
  4. 毫米波雷达数据处理_基于毫米波雷达的桥梁静挠度采集系统
  5. 工具栏自定义_EXCEL LESSON12 自定义功能区菜单及工具栏(1/3)
  6. 【牛客挑战赛31D】 雷的打字机
  7. python MySQL 插入Elasticsearch
  8. 自我总结和学习表单提交的几种方式 (一)
  9. bzoj 3675: [Apio2014]序列分割
  10. atan与atan2的区别