RedisLockConfig
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.scripting.support.ResourceScriptSource;
import pers.jaye.lock.lock.RedisLock;
import java.io.Serializable;
import java.util.concurrent.TimeUnit;
@Configuration
public class RedisLockConfig {
@Bean(name = "luaRedisTemplate")
public RedisTemplate initRedisTemplate(LettuceConnectionFactory connectionFactory) {
RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
redisTemplate.setConnectionFactory(connectionFactory);
return redisTemplate;
}
@Bean
public DefaultRedisScript<String> defaultRedisScript() {
DefaultRedisScript<String> defaultRedisScript = new DefaultRedisScript<>();
defaultRedisScript.setResultType(String.class);
defaultRedisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/unlock.lua")));
return defaultRedisScript;
}
@Bean("parkRedisLock")
public RedisLock redisLock(
@Qualifier("luaRedisTemplate") RedisTemplate redisTemplate,
DefaultRedisScript defaultRedisScript) {
return new RedisLock("PER.JAYE", 1, TimeUnit.MINUTES, redisTemplate, defaultRedisScript);
}
}
RedisLock实现
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.ReturnType;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class RedisLock implements Lock {
private String key;
private long timeout;
private TimeUnit timeUnit;
private RedisTemplate redisTemplate;
private DefaultRedisScript defaultRedisScript;
private ThreadLocal<String> localUUID = new ThreadLocal<>();
public RedisLock(String key, long timeout, TimeUnit timeUnit, RedisTemplate redisTemplate, DefaultRedisScript defaultRedisScript) {
this.key = key;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.redisTemplate = redisTemplate;
this.defaultRedisScript = defaultRedisScript;
}
@Override
public void lock() {
if (tryLock()) {
return;
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
lock();
}
@Override
public boolean tryLock() {
String uuid = UUID.randomUUID().toString();
if (redisTemplate.opsForValue().setIfAbsent(key, uuid, timeout, timeUnit)) {
localUUID.set(uuid);
return true;
}
return false;
}
@Override
public void unlock() {
String value = localUUID.get();
redisTemplate.execute((RedisConnection connection) -> connection.eval(
defaultRedisScript.getScriptAsString().getBytes(),
ReturnType.INTEGER,
1,
key.getBytes(),
value.getBytes()));
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
}
lua脚本
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
测试
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.locks.Lock;
@RestController
public class LockController {
private static long count = 20;
private CountDownLatch countDownLatch = new CountDownLatch(5);
@Resource(name = "parkRedisLock")
private Lock lock;
@RequestMapping(value = "/sale", method = RequestMethod.GET)
public Long query() throws InterruptedException {
count = 20;
countDownLatch = new CountDownLatch(5);
System.out.println("-------共20张票,分五个窗口开售-------");
new PlusThread().start();
new PlusThread().start();
new PlusThread().start();
new PlusThread().start();
new PlusThread().start();
return count;
}
public class PlusThread extends Thread {
private int amount = 0;
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "开始售票");
countDownLatch.countDown();
if (countDownLatch.getCount() == 0) {
System.out.println("----------售票结果------------------------------");
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (count > 0) {
lock.lock();
try {
if (count > 0) {
amount++;
count--;
}
} finally {
lock.unlock();
}
try {
Thread.sleep(10);
} catch (Exception e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "售出" + (amount) + "张票");
}
}
}
官方发布的redis分布式客户端
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.10.7</version>
</dependency>