- 单机部署的应用
用jvm本地锁,如synchronized和ReentrantLock。 - 集群部署的应用
用乐观锁
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
package com.example.mybatisplus.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 库存控制器,测试用
*/
@RestController
@RequestMapping("stock")
public class StockController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 测试redis乐观锁
*/
@GetMapping("/deduct")
public void deduct(){
redisTemplate.execute(new SessionCallback<Object>() {
@Override
public Object execute(RedisOperations operations) throws DataAccessException {
//监听stock
operations.watch("stock");
String stock = operations.opsForValue().get("stock").toString();
if(stock != null && stock.length() > 0){
Integer value = Integer.parseInt(stock);
if(value > 0){
//开启事务
operations.multi();
operations.opsForValue().set("stock",String.valueOf(--value));
//执行事务
List exec = operations.exec();
if(exec == null || exec.size() == 0){
try {
Thread.sleep(40);
} catch (InterruptedException e) {
e.printStackTrace();
}
deduct();
}
return exec;
}
}
return null;
}
});
}
}
用分布式锁 需满足:
1、防死锁,死锁产生原因是服务器宕机后,设置的锁无法释放,造成死锁。
2、防误删,误删产生原因:理论上加上自动续期的功能后就不会误删,但是如果别的应用程序没有加锁的步骤,只有解锁的步骤,直接释放锁就造成了误删。
3、原子性,加锁解锁的逻辑如果有好几条指令要一起执行,不能被其他线程分开,采用lua脚本实现原子性
lua脚本可以一次性发送多条指令给redis所以能保证判断和删除的原子性,redis默认支持lua脚本,所以可以不配置lua环境的情况下使用lua脚本
redis中使用lua脚本 eval “lua脚本” 参数名个数 参数名列表(空格分隔) 参数值列表(空格分隔)
例:eval “redis.call(‘set’,KEYS[1],ARGV[1])” 1 lock 123 设置一个键值对lock 123
4、可重入,业务逻辑中如果还有获取锁的需求,同一线程要能再次获取锁。
5、自动续期,要自动续期原因是:过期时间设置过短,业务逻辑还没走完,锁就释放了,那么跟没加锁是一样的。
代码如下:
StockController.java
package com.example.mybatisplus.controller;
import com.example.mybatisplus.lock.DistributedLockClient;
import com.example.mybatisplus.lock.DistributedRedisLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DataAccessException;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 库存控制器,测试用
*/
@RestController
@RequestMapping("stock")
public class StockController {
@Autowired
private StringRedisTemplate redisTemplate;
/**
* 测试redis乐观锁
*/
@GetMapping("/deduct")
@Autowired
private DistributedLockClient distributedLockClient;
@GetMapping("/deduct3")
public void deduct3(){
DistributedRedisLock lock = distributedLockClient.getRedisLock("lock");
//加锁
lock.lock();
try{
//查库存
String stock = redisTemplate.opsForValue().get("stock");
//判断库存是否充足
if(stock != null && stock.length() > 0) {
Integer value = Integer.parseInt(stock);
if (value > 0) {
//减库存
redisTemplate.opsForValue().set("stock", String.valueOf(--value));
}
}
}finally {
//解锁
lock.unlock();
}
}
}
DistributedRedisLock.java
package com.example.mybatisplus.lock;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class DistributedRedisLock implements Lock {
private StringRedisTemplate redisTemplate;
private String lockName;
private String uuid;
private long expire = 30L;
public DistributedRedisLock(StringRedisTemplate redisTemplate, String lockName,String uuid) {
this.redisTemplate = redisTemplate;
this.lockName = lockName;
this.uuid = uuid + ":" + Thread.currentThread().getId();
}
@Override
public void lock() {
this.tryLock();
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
this.tryLock(-1L,TimeUnit.SECONDS);//-1L代表不设置过期时间
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
/**
* 加锁
* @param time 自定义的过期时间
* @param unit 自定义的过期时间的单位
* @return
* @throws InterruptedException
*/
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
if(time != -1L){
this.expire = unit.toSeconds(time);
}
String script = "if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
"then " +
"redis.call('hincrby',KEYS[1],ARGV[1],1) " +
"redis.call('expire',KEYS[1],ARGV[2]) " +
"return 1 " +
"else " +
"return 0 " +
"end";
while(!redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class), Arrays.asList(lockName),uuid,String.valueOf(expire))){
Thread.sleep(50);
}
//加锁成功,开启定时器自动续期
this.renewExpire();
return true;
}
/**
* 解锁
*/
@Override
public void unlock() {
String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 0 " +
"then " +
"return nil " +
"elseif redis.call('hincrby',KEYS[1],ARGV[1],-1) == 0 " +
"then " +
"return redis.call('del',KEYS[1]) " +
"else " +
"return 0 " +
"end";
Long flag = redisTemplate.execute(new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockName),uuid);
if(flag == null){
throw new IllegalMonitorStateException("this lock doesn't belong to you!");
}
}
@Override
public Condition newCondition() {
return null;
}
/**
* 延迟 1/3过期时间 执行续期的一次性定时任务
*/
private void renewExpire(){
String script = "if redis.call('hexists',KEYS[1],ARGV[1]) == 1 " +
"then " +
"return redis.call('expire',KEYS[1],ARGV[2]) " +
"else " +
"return 0 " +
"end";
new Timer().schedule(new TimerTask(){
public void run(){
if(redisTemplate.execute(new DefaultRedisScript<>(script,Boolean.class),Arrays.asList(lockName),uuid,String.valueOf(expire))){
renewExpire();
}
}
},this.expire*1000/3);
}
}
DistributedLockClient.java
package com.example.mybatisplus.lock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.util.UUID;
@Component
public class DistributedLockClient {
@Autowired
private StringRedisTemplate redisTemplate;
private String uuid = UUID.randomUUID().toString();
public DistributedRedisLock getRedisLock(String lockName){
return new DistributedRedisLock(redisTemplate,lockName,uuid);
}
}
使用Redisson的分布式锁
导入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.4</version>
</dependency>
创建配置类
package com.example.mybatisplus.config;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissionClient(){
Config config = new Config();
config.useSingleServer()
.setAddress("redis://127.0.0.1:6379"); //指定redis服务器地址
// .setDatabase(0)//指定redis数据库编号
// .setUsername("").setPassword("")//设置redis的用户名密码
// .setConnectionMinimumIdleSize(10)//连接池最小空闲连接数
// .setConnectionPoolSize(50)//连接池最大线程数
// .setIdleConnectionTimeout(60000)//线程超时时间
// .setConnectTimeout(50)//客户端程序获取redis连接的超时时间
// .setTimeout()//redis服务器响应的超时时间
// config.useClusterServers().addNodeAddress("redis://192.168.1.8:6379","redis://192.168.1.10.6379");//集群模式
return Redisson.create();
}
}
使用
@Autowired
private RedissonClient redissonClient;
@GetMapping("/deduct4")
public void deduct4(){
RLock redissonLock = redissonClient.getLock("lock");
//加锁
redissonLock.lock();
try{
//查库存
String stock = redisTemplate.opsForValue().get("stock");
//判断库存是否充足
if(stock != null && stock.length() > 0) {
Integer value = Integer.parseInt(stock);
if (value > 0) {
//减库存
redisTemplate.opsForValue().set("stock", String.valueOf(--value));
}
}
test();
}finally {
//解锁
redissonLock.unlock();
}
}
使用redisson实现公平锁
/**
* 公平锁,哪个线程先请求,就先获取锁。
* @param id
*/
@GetMapping("/fairLock")
public void testFairLock(String id){
RLock lock = redissonClient.getFairLock("fairLock");
lock.lock();
System.out.println("我是。。。。。。。。。"+id+System.currentTimeMillis());
lock.unlock();
}
使用redisson实现读锁
/**
* 读锁,可以并发,线程之间不互斥
*/
@GetMapping("/readLock")
public void testReadLock(){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("lock");
readWriteLock.readLock().lock();
System.out.println("读锁。。。。。。。。。。。。。。");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock();
}
}
使用redisson实现写锁
/**
* 写锁,不可以并发,线程之间互斥
*/
@GetMapping("/writeLock")
public void testWriteLock(){
RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("lock");
readWriteLock.writeLock().lock();
System.out.println("写锁。。。。。。。。。。。。。。");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.writeLock().unlock();
}
}