mysql锁表创建语句如下:
create table mysql_lock
(
id bigint auto_increment comment '主键'
primary key,
lock_key varchar(100) default '0' not null comment '分布式锁key',
end_time timestamp default CURRENT_TIMESTAMP not null comment '失效时间',
client_id varchar(100) default '' not null comment '获取锁的ip + threadId',
version int(4) default 1 not null comment '版本号',
times int(4) default 0 not null comment '重入次数',
create_time timestamp default CURRENT_TIMESTAMP not null comment '创建时间',
update_time timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间',
constraint uniq_lock_key
unique (lock_key)
)
comment '分布式锁表';
java代码工具类
服务侧
public class MysqlLocker {
@Resource
private MLockDao mLockDao;
/**
* 加锁
* @param key
* @param secondes
* @return 成功/失败
*/
@Transactional(rollbackFor = Throwable.class)
public Boolean tryLock(String key,Long secondes){
MLock lock = mLockDao.selectByKey(key);
String clientId = currentId();
Date endTime = new Date(System.currentTimeMillis() + secondes * 1000);
Boolean result = Boolean.FALSE;
if(Objects.isNull(lock)){
// 无冲突 直接插入
lock = new MLock();
lock.setLockKey(key);
lock.setEndTime(endTime);
lock.setClientId(clientId);
// 防止极端情况 初始版本号一致 导致误解锁
lock.setVersion(new Random().nextInt(100));
try{
int rst = mLockDao.insert(lock);
// 插入成功 无碰撞获取锁成功
result = rst > 0;
}catch (Exception e){
// 唯一索引冲突 枷锁失败
result = Boolean.FALSE;
}
}else {
// 锁过期判定
if(System.currentTimeMillis() >= lock.getEndTime().getTime()){
int rst = mLockDao.updateByKey(key,endTime,clientId, lock.getVersion(), lock.getVersion() + 1,0);
if(rst > 0){
result = Boolean.TRUE;
}
}
}
return result;
}
/**
* 解锁
* @param key
*/
@Transactional(rollbackFor = Throwable.class)
public void unLock(String key){
MLock lock = mLockDao.selectByKey(key);
if(Objects.nonNull(lock)){
// 判断是否当前线程拥有锁
String clientId = currentId();
if(!clientId.equals(lock.getClientId())){
// 非当前线程拥有 不可解锁
return;
}
// 当前线程拥有 则直接删除记录
int rst = mLockDao.deleteByKey(key,lock.getVersion());
if(rst > 0){
return;
}
}
}
private String currentId(){
String ip = null;
try {
ip = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
// 获取本地ip异常
ip = "";
}
return ip + "_" + Thread.currentThread().getId();
}
}
dao层
public interface MLockDao {
int insert(MLock lock);
int updateByKey(@Param("key") String key,
@Param("endTime") Date endTime,
@Param("clientId") String clientId,
@Param("version") int version,
@Param("newVersion") int newVersion,
@Param("times") int times);
MLock selectByKey(@Param("key") String key);
int deleteByKey(@Param("key") String key,
@Param("version") int version);
}
mapper
<mapper namespace="com.example.demo.dao.primary.MLockDao">
<sql id="fields">
id,
lock_key as lockKey,
end_time as endTime,
client_id as clientId,
version,
times,
create_time as createTime,
update_time as updateTime
</sql>
<insert id="insert" parameterType="com.example.demo.utils.Lock.MLock"
useGeneratedKeys="true" keyColumn="id" keyProperty="id">
insert into m_lock (
lock_key,
end_time,
client_id,
version
)
values(
#{lockKey},
#{endTime},
#{clientId},
#{version}
)
</insert>
<select id="selectByKey" resultType="com.example.demo.utils.Lock.MLock">
select
<include refid="fields"/>
from m_lock
where lock_key = #{key}
</select>
<update id="updateByKey" >
update m_lock
set
end_time = #{endTime},
client_id = #{clientId},
version = #{newVersion},
times = #{times}
where lock_key = #{key} and version = #{version}
</update>
<delete id="deleteByKey">
delete from m_lock
where lock_key = #{key} and version = #{version}
</delete>
</mapper>
最顶层工具类
public class LockUtil implements InitializingBean {
private static final Logger logger = LoggerFactory.getLogger(LockUtil.class);
@Resource
private MysqlLocker mLock;
private static MysqlLocker LOCK;
/**
* 初始化
* @throws Exception
*/
@Override
public void afterPropertiesSet() throws Exception {
LockUtil.LOCK = mLock;
}
/**
* 加锁
* @param key
* @param secondes
* @return
*/
public static boolean tryLock(String key, Long secondes){
return LOCK.tryLock(key,secondes);
}
/**
* 解锁
* @param key
*/
public static void unLock(String key){
LOCK.unLock(key);
}
/**
* 加锁执行 并获取结果
* @param key
* @param secondes
* @param function
* @return
* @param <T>
*/
public static <T> T execute(String key, Long secondes, Supplier<T> function){
Boolean loked = Boolean.FALSE;
try {
if(loked = LOCK.tryLock(key,secondes)){
if(logger.isDebugEnabled()){
logger.debug("分布式加锁成功,key:{} expir:{}",key,secondes);
}
return function.get();
}
throw new BizException("操作频繁,请稍后再试");
} catch (Exception e) {
logger.error("分布式加锁操作失败,key:{} ,error:{}",key,e.getMessage(),e);
throw new BizException("操作频繁,请稍后再试", e);
}finally {
if(loked){
LOCK.unLock(key);
if(logger.isDebugEnabled()){
logger.debug("分布式解锁成功,key:{}",key);
}
}
}
}
}