select * from xxx for update 悲观锁方式。
缺点:会导致数据库锁占用很长时间,同时会长时间占用数据库连接。
@Component
public class Example {
@Autowired
private Lock lock;
public void test() {
// 尝试获取锁
LLock llock = lock.acquire(key, Duration.ofSeconds(10));
if (Objects.isNull(llock)) {
throw new BusinessException("其他人正在处理中,请稍后重试");
}
try {
// 业务代码
} finally {
// 解除锁
lock.release(llock);
}
}
}
CREATE TABLE `distribute_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`lock_key` varchar(50) NOT NULL COMMENT '锁记录key',
`token` varchar(50) NOT NULL COMMENT '锁的token,防止误删其他人的锁',
`thread_id` varchar(50) NOT NULL COMMENT '获取锁的线程id',
`expire` bigint(20) NOT NULL COMMENT '锁的失效时间,时间戳',
PRIMARY KEY (`id`),
UNIQUE KEY `distribute_lock_UN` (`lock_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='分布式锁';
tips:表名切勿使用lock,使用关键字语法会报错。在oracle里,resource也是关键字,mysql里不是。



后边会再实现个redis版本的。
public interface Lock {
/**
* 尝试获取锁,
*
* @param key 锁定的key
* @param expiration 锁定的时间
* @return 锁定失败返回null,成功返回LLock锁对象
*/
LLock acquire(String key, Duration expiration);
/**
* 释放锁
*
* @param lock 锁对象
* @return 是否成功
*/
boolean release(LLock lock);
}
String token = fastSimpleUUID();
int row = INSERT ignore INTO distribute_lock (lock_key, token, expire, thread_id) VALUES (?, ?, ?, ?);
if (row == 0) {
return null;
}
// 续约线程
ScheduledFuture scheduledFuture = scheduleLockRefresh(refresh)
return new LLock(key,token,scheduledFuture);
客户端设置的锁,必须由自己解开。即会check key和token。
cancelSchedule(lock.getScheduledFuture());
DELETE FROM distribute_lock WHERE lock_key = ? AND token = ?;
当客户端发现在锁的租期内无法完成操作时,就需要延长锁的持有时间,进行续租(renew)。同解锁一样,客户端应该只能续租自己持有的锁。续租需要每个锁后台定时任务时间建议是锁的1/3去续租。
UPDATE distribute_lock SET expire = ? WHERE lock_key = ? AND token = ?;
整体代码在:https://gitee.com/lakernote/easy-admin/tree/master/src/main/java/com/laker/admin/framework/lock
优点:
缺点:
参考: