作为编程人员,需要关注的一个事情,就是并发运行带来的问题
注意:分布式锁也是不可避免的事情,但是目前作为分布式锁的组件,主推redis,这是一个非常常见的分布式锁,也是程序员必须掌握的。
并发编程需要注意的:
如下图,多个线程去抢占一个资源:
解决方法,对线程加锁。
总结来说,Lock与synchronized有以下区别:
在高可用的环境下,就会出现多个程序抢占一个资源的问题,普通的代码锁明显是无法解决这个问题,这个时候就必须采用分布式锁。
如下图,采用redis作为分布式锁:
在分布式系统环境下,一个方法在同一时间只能被一个机器的一个线程执行;
分布式锁的实现又哪些?
1数据库层面的乐观锁;
2基于zookeeper的节点的实现;
3基于redis中间件的实现
性能比较:redis中间件>zookeeper=数据库
可靠性比较:zookeeper>redis中间件>数据库
相信各位小伙伴在学习Redis时,都了解到Redis不仅仅是一个内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。
redis常见客户端:
Redis官方对Java 语言的封装框架推荐的有十多种(Redis 官网),主要是Jedis 、Redisson。
根据redis官网 https://redis.io/clients#java 所言,redisson是使用在 分布式和可伸缩的Java数据结构在Redis服务器上的
<dependencies>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-webartifactId>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.9.1version>
dependency>
<dependency>
<groupId>org.projectlombokgroupId>
<artifactId>lombokartifactId>
<optional>trueoptional>
dependency>
dependencies>
@RestController
public class NoDistributeController {
/**
* 无分布式锁
*/
@Autowired
NoDistributeService redisDistributeService;
/**
* 查询剩余订单结果接口
* @return
*/
@GetMapping("/query")
public String query() {
return redisDistributeService.queryMap();
}
/**
* 下单接口
* @return
*/
@GetMapping("/order")
public String order() {
redisDistributeService.order();
return redisDistributeService.queryMap();
}
}
@Service
public class NoDistributeService {
/**
* 模拟商品信息表
*/
private static Map<String,Integer> products;
/**
* 模拟库存
*/
private static Map<String,Integer> stock;
/**
* 订单
*/
private static Map<String,String> orders;
static {
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
//模拟订单表数据 订单编号 苹果 库存 100000
products.put("苹果",100000);
//模拟库存表数据 订单编号 苹果 库存100000
stock.put("苹果",100000);
}
/**
* 模拟查询秒杀成功返回的信息
* @return 返回拼接的秒杀商品结果字符串
*/
public String queryMap() {
String pid = "苹果";
return "秒杀商品限量:" + products.get(pid) + "份,还剩:"+stock.get(pid) +"份,成功下单:"+orders.size() + "人";
}
/**
* 下单
*/
public void order() {
String pid = "苹果";
//从库存表中获取库存余量
int stockNum = stock.get(pid);
//如果库存为0 则输出库存不足
if(stockNum == 0) {
System.out.println("商品库存不足");
}else{
//如果有库存
//往订单表中插入数据 生成UUID作为用户ID pid
orders.put(UUID.randomUUID().toString(),pid);
//线程休眠 模拟其他操作
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存操作
stock.put(pid,stockNum-1);
}
}
}
2秒,下单1000次
查看结果:http://localhost:8080/query
可以看出, 下单了994个人,而订单只下了29份,严重出现问题。
Config config = new Config();
config.useClusterServers()
.setScanInterval(2000) // cluster state scan interval in milliseconds
.addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001")
.addNodeAddress("redis://127.0.0.1:7002");
RedissonClient redisson = Redisson.create(config);
RLock lock = redisson.getLock("myLock");
// traditional lock method
//lock方法是直接加锁,如果锁已被占用,则直接线程阻塞,进行等待,直到锁被占用方释放。
lock.lock();
// or acquire lock and automatically unlock it after 10 seconds
lock.lock(10, TimeUnit.SECONDS);
// or wait for lock aquisition up to 100 seconds
// and automatically unlock it after 10 seconds
//tryLock方法则是设定了waitTime(等待时间),在这个等待时间没到前,也是线程阻塞并反复去获取锁,直到取到锁或等待时间超时,则返回false。
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
try {
...
} finally {
lock.unlock();
}
}
更多加锁方法可以参考 https://github.com/redisson/redisson/wiki/8.-distributed-locks-and-synchronizers
package com.dislock.redis.lock;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.config.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
@Service
public class NoDistributeService {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* 模拟商品信息表
*/
private static Map<String, Integer> products;
/**
* 模拟库存
*/
private static Map<String, Integer> stock;
/**
* 订单
*/
private static Map<String, String> orders;
/**
* 获取锁
*/
private static Redisson redisson;
static {
products = new HashMap<>();
stock = new HashMap<>();
orders = new HashMap<>();
//模拟订单表数据 订单编号 苹果 库存 100000
products.put("苹果", 100000);
//模拟库存表数据 订单编号 苹果 库存100000
stock.put("苹果", 100000);
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost:6379");
redisson = (Redisson) Redisson.create(config);
}
/**
* 模拟查询秒杀成功返回的信息
*
* @return 返回拼接的秒杀商品结果字符串
*/
public String queryMap() {
String pid = "苹果";
return "秒杀商品限量:" + products.get(pid) + "份,还剩:" + stock.get(pid) + "份,成功下单:" + orders.size() + "人";
}
/**
* 下单
*/
public void order() {
String pid = "苹果";
//从库存表中获取库存余量
int stockNum = stock.get(pid);
// 获得锁对象实例
RLock lock = redisson.getLock("lock_Key");
boolean res = false;
try {
res = lock.tryLock();
//locked = lock.tryLock(1,2,TimeUnit.MINUTES);
if (res) {
try {
//如果库存为0 则输出库存不足
if (stockNum == 0) {
System.out.println("商品库存不足");
} else {
//如果有库存
//往订单表中插入数据 生成UUID作为用户ID pid
orders.put(UUID.randomUUID().toString(), pid);
//线程休眠 模拟其他操作
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存操作
stock.put(pid, stockNum - 1);
}
} finally {
logger.info("释放锁");
lock.unlock();
}
}
} catch (Exception e) {
logger.info("获取锁有误");
}
}
}
本人长期从事java开发,有疑问,请留言,我会及时改正, 谢谢