import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.connection.RedisStringCommands;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.types.Expiration;
import org.springframework.data.redis.serializer.RedisSerializer;
import saas.common.util.RedisKeyUtils;
import java.util.concurrent.TimeUnit;
/**
* 基于线程本地实现的线程级简单锁
*
* @author Terry
*/
public class OrderLockHelper {
private static final Logger logger = LoggerFactory.getLogger(OrderLockHelper.class);
/**
* 资源ID前缀
*/
private static final String RESOURCE_ID_PREFIX = "orderLock";
/**
* 默认超时时间 60s
*/
private static final int DEFAULT_TIMEOUT_SECONDS = 60;
/**
* Owner's lock threadLocal
*/
private static final ThreadLocal<String> KEY_INHERITABLE_THREAD_LOCAL = new InheritableThreadLocal<>();
/**
* Redis Template
*/
private static StringRedisTemplate orderLockRedisTemplate;
/**
* keySerializer
*/
private static RedisSerializer<String> keySerializer;
/**
* valueSerializer
*/
private static RedisSerializer<String> valueSerializer;
/**
* 在构造函数自动注入
*
* @param orderLockRedisTemplate redis对象
*/
@SuppressWarnings("unchecked")
@Autowired
private OrderLockHelper(@Qualifier("orderLockRedisTemplate") final StringRedisTemplate orderLockRedisTemplate) {
OrderLockHelper.orderLockRedisTemplate = orderLockRedisTemplate;
OrderLockHelper.keySerializer = (RedisSerializer<String>) orderLockRedisTemplate.getKeySerializer();
OrderLockHelper.valueSerializer = (RedisSerializer<String>) orderLockRedisTemplate.getValueSerializer();
}
/**
* 尝试当前线程独占订单Id关联操作
*
* @param orderId 订单Id
* @param lockReason 独占锁的原因
* @return
*/
public static boolean tryLock(final String orderId, final String lockReason) {
return tryLock(orderId, lockReason, DEFAULT_TIMEOUT_SECONDS);
}
/**
* 尝试当前线程独占订单Id关联操作,并限定超时时间
*
* @param orderId 订单Id
* @param lockReason 独占锁的原因
* @param timeoutSeconds 单位为秒
* @return
*/
public static boolean tryLock(final String orderId, final String lockReason, final int timeoutSeconds) {
String lockKey = RedisKeyUtils.buildKey(RESOURCE_ID_PREFIX, orderId);
byte[] rawKey = keySerializer.serialize(lockKey);
byte[] rawValue = valueSerializer.serialize(lockReason);
Boolean result = orderLockRedisTemplate.execute((RedisCallback<Boolean>) connection ->
connection.set(rawKey, rawValue, Expiration.from(timeoutSeconds, TimeUnit.SECONDS), RedisStringCommands.SetOption.SET_IF_ABSENT)
);
if (result != null && result) {
KEY_INHERITABLE_THREAD_LOCAL.set(lockKey);
return true;
}
return false;
}
/**
* 解除当前线程独占订单Id关联操作
* XXX: 非线程拥有的锁unlock无效
*
* @return
*/
public static void unlock() {
String lockKey = KEY_INHERITABLE_THREAD_LOCAL.get();
if (lockKey != null) {
Boolean result = orderLockRedisTemplate.delete(lockKey);
if (result != null && !result) {
logger.error("解锁超时: lockKey={},请注意控制加锁区域内代码!!!", lockKey);
}
KEY_INHERITABLE_THREAD_LOCAL.remove();
}
}
}
版权归属:
Terry
许可协议:
本文使用《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》协议授权
评论区