目录
- 4.1. 基本思路
- 4.2. 代码实现
- 4.3 缺陷及解决方案
4.1. 基本思路
synchronized关键字和ReetrantLock锁都是独占排他锁,即多个线程争抢一个资源时,同一时刻只有一个线程可以抢占该资源,其他线程只能阻塞等待,直到占有资源的线程释放该资源
利用唯一键索引不能重复插入的特点实现
4.2. 代码实现
1、创建锁表,和对应的实体类
CREATE TABLE `tb_lock` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`lock_name` varchar(50) NOT NULL COMMENT '锁名',
`class_name` varchar(100) DEFAULT NULL COMMENT '类名',
`method_name` varchar(50) DEFAULT NULL COMMENT '方法名',
`server_name` varchar(50) DEFAULT NULL COMMENT '服务器ip',
`thread_name` varchar(50) DEFAULT NULL COMMENT '线程名',
`create_time` timestamp NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '获取锁时间',
`desc` varchar(100) DEFAULT NULL COMMENT '描述',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_unique` (`lock_name`)
) ENGINE=InnoDB AUTO_INCREMENT=1332899824461455363 DEFAULT CHARSET=utf8;
@Data
@AllArgsConstructor
@NoArgsConstructor
@TableName("tb_lock")
public class Lock {
private Long id;
private String lockName;
private String className;
private String methodName;
private String serverName;
private String threadName;
private Date createTime;
private String desc;
}
LockMapper接口:
public interface LockMapper extends BaseMapper<Lock> {
}
改造StockService:
@Service
public class StockService {
@Autowired
private StockMapper stockMapper;
@Autowired
private LockMapper lockMapper;
/**
* 数据库分布式锁
*/
public void checkAndLock() {
// 加锁
Lock lock = new Lock(null, "lock", this.getClass().getName(), new Date(), null);
try {
this.lockMapper.insert(lock);
// 先查询库存是否充足
Stock stock = this.stockMapper.selectById(1L);
// 再减库存
if (stock != null && stock.getCount() > 0){
stock.setCount(stock.getCount() - 1);
this.stockMapper.updateById(stock);
}
} catch (Exception ex) {
// 获取锁失败,则重试
try {
Thread.sleep(50);
this.checkAndLock();
} catch (InterruptedException e) {
e.printStackTrace();
}
} finally {
// 释放锁
this.lockMapper.deleteById(lock.getId());
}
}
}
测试:
使用Jmeter压力测试结果:
查看redis 库存为0,表明mysql锁确实起到作用了
4.3 缺陷及解决方案
上述mysql实现分布式锁的过程中还存在一些问题!!
1、防止死锁
客户端程序获取到锁之后,客户端程序宕机的话,会导致持有的锁不会释放(死锁)
解决方案:给锁记录添加一个释放锁的时间戳,启一个定时任务清理过期的锁记录
2、防止误删
有锁记录是唯一的,mysql作为分布式锁不存在误删问题
3、可重入
记录获取锁的主机信息和线程信息,如果相同线程要获取锁,直接重入。
4、锁的自动续期
服务器内的定时器自动重置锁的过期时间
5、单机故障
搭建mysql主备
6、集群情况下锁失效问题
当一个线程获取到锁之后,此时主节点宕机,并且mysql主节点的数据还没有同步到从节点。又一个线程尝试去获取锁吗,那么将会获取锁成功