大概有三种:
利用主键唯一的特性,如果有多个请求同时提交到数据库的话,数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可(性能上依赖数据库)。
CREATE TABLE `data_base_lock` (
`id` varchar(64) NOT NULL,
`lock_key` varchar(500) NOT NULL DEFAULT '' COMMENT '锁名称',
`time_out` int(11) NOT NULL DEFAULT '0' COMMENT '锁存活时间(单位:秒)',
`remarks` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`update_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '最后更新时间',
`create_date` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_by` varchar(64) NOT NULL DEFAULT '',
`create_by` varchar(64) NOT NULL DEFAULT '',
`del_flag` char(1) NOT NULL DEFAULT '0' COMMENT '0正常,1删除',
PRIMARY KEY (`lock_key`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC COMMENT='数据库分布式锁表';
当我们想要获得锁时,可以插入一条数据:
INSERT INTO data_base_lock ( id, lock_key, time_out, create_by, create_date, update_by, update_date, del_flag ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ? )
注意:在表data_base_lock中,lock_key字段做了唯一性约束,这样如果有多个请求同时提交到数据库的话,数据库可以保证只有一个操作可以成功(其它的会报错:### Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry 'lock-key' for key 'PRIMARY',那么我们就可以认为操作成功的那个请求获得了锁。
package com.example.mybatiesplus.utils;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.example.mybatiesplus.entity.DataBaseLock;
import com.example.mybatiesplus.mapper.DataBaseLockMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;
/**
* @DESCRIPTION 基于数据库 乐观锁的 分布式锁
*
* 利用主键唯一的特性,如果有多个请求同时提交到数据库的话,
* 数据库会保证只有一个操作可以成功,那么我们就可以认为操作成功的那个线程获得了该方法的锁,
* 当方法执行完毕之后,想要释放锁的话,删除这条数据库记录即可。
* @Author lst
* @Date 2020-05-20 18:00
*/
@Slf4j
@Component
public class DataBaseLockUtil {
@Autowired
private DataBaseLockMapper dataBaseLockMapper;
@Scheduled(cron = "0 10 * * * ?")
private void checkSynTaskKeyIsExpire() {
log.info("=========开始检查数据库分布式锁的过期时间=======");
List taskLocks = dataBaseLockMapper.selectList(null);
//筛选出过期的key
List dataBaseLockList = taskLocks.stream().filter(dataBaseLock ->
System.currentTimeMillis() - dataBaseLock.getUpdateDate().getTime() - dataBaseLock.getTimeOut() * 1000 > 0).collect(Collectors.toList());
//删除过期的锁
dataBaseLockList.forEach(dataBaseLock -> {
dataBaseLockMapper.deleteById(dataBaseLock.getId());
});
log.info("=========trs_task_lock 删除{}条 过期的key=======",dataBaseLockList.size());
}
/**
* 获取分布式锁
* 根据插入语句中的主键冲突,相同主键的多次插入操作,只会有一次成功,成功的就获得分布式锁执行任务的权利
* @author lst
* @date 2020-5-21 8:53
* @param key 锁名称
* @param timeOut 锁超时时间
* @return boolean
*/
public boolean tryLock(String key,int timeOut){
DataBaseLock dataBaseLock = new DataBaseLock();
dataBaseLock.setLockKey(key);
dataBaseLock.setTimeOut(timeOut);
int flag = 0;
try {
flag = dataBaseLockMapper.insert(dataBaseLock);
}catch (Exception e){
log.info("{} 其他机器已经在执行",key);
log.info("错误信息:{}",e.toString());
}
return flag == 1;
}
/**
* 删除分布式锁
* @author lst
* @date 2020-5-21 8:59
* @param key 锁名称
* @return boolean
*/
public boolean delLock(String key){
int flag = 0;
try{
flag = dataBaseLockMapper.deleteAll(key);
}catch (Exception e){
log.info("错误信息:{}",e.toString());
}
return flag == 1;
}
/**
* 获取锁对象
* @author lst
* @date 2020-5-21 9:05
* @param key
* @return com.example.mybatiesplus.entity.DataBaseLock
*/
public DataBaseLock getLock(String key){
return dataBaseLockMapper.selectOne(new QueryWrapper().lambda().eq(DataBaseLock::getLockKey,key));
}
}
5、DataBaseLock实体类
package com.example.mybatiesplus.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.example.mybaties.entity.BasePlusEntity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.io.Serializable;
import java.util.Date;
/**
*
* 数据库分布式锁表
*
*
* @Author lst
* @Date 2020-05-20 18:00
*/
@TableName("data_base_lock")
@Data
public class DataBaseLock extends BasePlusEntity {
/**
*锁名称
*/
@TableField("lock_key")
private String lockKey;
/**
*锁存活时间(单位:秒) 避免死锁
*/
@TableField("time_out")
private int timeOut;
}
6、DataBaseLockMapper和DataBaseLockMapper.xml
package com.example.mybatiesplus.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.mybatiesplus.entity.DataBaseLock;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
/**
* @DESCRIPTION
* @Author lst
* @Date 2020-05-20 18:00
*/
@Mapper
public interface DataBaseLockMapper extends BaseMapper {
int deleteAll(@Param("lockKey") String lockKey);
}
delete from data_base_lock
where lock_key = #{lockKey}
7、写一个测试类
package com.example.mybatiesplus.controller;
import com.example.mybatiesplus.result.BaseResponse;
import com.example.mybatiesplus.result.ResultGenerator;
import com.example.mybatiesplus.utils.DataBaseLockUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @DESCRIPTION 测试类
* @Author lst
* @Date 2020-05-24
*/
@RestController
@RequestMapping("/test")
@Api(value = "TestController", tags = "测试类")
@Slf4j
public class TestController {
@Autowired
private DataBaseLockUtil dataBaseLockUtil;
public final static String LOCK_KEY = "lock-key";
public final static int DEFAULT_TIME_OUT = 60 * 10;
/**
* 通过数据库分布式锁高并发测试
* @author lst
* @date 2020-5-24 17:32
* @param
* @return com.example.mybatiesplus.result.BaseResponse
*/
@GetMapping(value = "/dataBaseLock", produces = "application/json; charset=utf-8")
@ApiOperation(value = "通过数据库分布式锁高并发测试", notes = "通过数据库分布式锁高并发测试", code = 200, produces = "application/json")
public BaseResponse dataBaseLock() {
try{
log.info("============={} 线程访问开始============",Thread.currentThread().getName());
//TODO 获取分布式锁
boolean lock = dataBaseLockUtil.tryLock(LOCK_KEY,DEFAULT_TIME_OUT);
if (lock) {
log.info("线程:{},获取到了锁",Thread.currentThread().getName());
//TODO 获得锁之后可以进行相应的处理 睡一会
Thread.sleep(100);
log.info("======获得锁后进行相应的操作======" + Thread.currentThread().getName());
dataBaseLockUtil.delLock(LOCK_KEY);
log.info("============================={} 释放了锁" + Thread.currentThread().getName());
}
}catch (Exception e){
log.info("错误信息:{}",e.toString());
log.info("线程:{} 获取锁失败",Thread.currentThread().getName());
}
return ResultGenerator.genSuccessResult();
}
}
8、使用jmeter测试
9、测试数据
在dataBaseLockUtil.delLock(LOCK_KEY);锁未释放的测试下,可能看到后台日志只有线程51获取到了锁。
将dataBaseLockUtil.delLock(LOCK_KEY);释放开在测试,只要某个抢到锁的线程执行完毕并且释放了锁资源,其他的线程很快就会获取到锁。