1 悲观锁,排他锁,当数据上锁以后,其他线程不能去操作该数据,只能等待占用线程尽快结束占用。这种方法并发效率低。
测试代码:
@Autowired
private DataSourceTransactionManager txManager;
/**
* 悲观锁
*/
@Test
public void testPessimisticLock(){
//创建固定线程数为6的线程池
ExecutorService executorService = Executors.newFixedThreadPool(6);
executorService.submit(()->{
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 事物隔离级别,开启新事务
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
// 获得事务状态
TransactionStatus status = txManager.getTransaction(def);
logger.info("锁住id为1的用户信息");
userDao.selectUserInfoAndLock(1);
logger.info("开始睡眠");
try {
TimeUnit.SECONDS.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
txManager.commit(status);
logger.info("睡眠结束,即提交请求,锁释放");
});
executorService.submit(()->{
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("开始修改数据数据");
User updateUser=new User();
updateUser.setId(1);
updateUser.setName("456");
userDao.updateByPrimaryKeySelective(updateUser);
logger.info("结束修改数据");
});
executorService.shutdown();
while(true){
if(executorService.isTerminated()){
break;
}
}
logger.info("测试完成");
}
锁住的SQL
<select id="selectUserInfoAndLock" parameterType="java.lang.Integer"
resultType="com.test.model.User">
select
<include refid="Base_Column_List" />
from user
WHERE 1=1
AND id=#{id}
FOR UPDATE
</select>
执行结果:
2019-06-21 18:15:42.502 INFO 18368 --- [pool-2-thread-1] com.test.service.UserServiceTest : 锁住id为1的用户信息
2019-06-21 18:15:42.523 DEBUG 18368 --- [pool-2-thread-1] c.t.dao.UserDao.selectUserInfoAndLock : ==> Preparing: select id, uuid, `name`, `password` from user WHERE 1=1 AND id=? FOR UPDATE
2019-06-21 18:15:42.637 DEBUG 18368 --- [pool-2-thread-1] c.t.dao.UserDao.selectUserInfoAndLock : ==> Parameters: 1(Integer)
2019-06-21 18:15:42.647 DEBUG 18368 --- [pool-2-thread-1] c.t.dao.UserDao.selectUserInfoAndLock : <== Total: 1
2019-06-21 18:15:42.647 INFO 18368 --- [pool-2-thread-1] com.test.service.UserServiceTest : 开始睡眠
2019-06-21 18:15:44.062 INFO 18368 --- [pool-2-thread-2] com.test.service.UserServiceTest : 开始修改数据数据
2019-06-21 18:15:44.104 DEBUG 18368 --- [pool-2-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Preparing: update `user` SET `name` = ? where id = ?
2019-06-21 18:15:44.105 DEBUG 18368 --- [pool-2-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Parameters: 456(String), 1(Integer)
2019-06-21 18:16:12.648 INFO 18368 --- [pool-2-thread-1] com.test.service.UserServiceTest : 睡眠结束,即提交请求,锁释放
2019-06-21 18:16:12.673 DEBUG 18368 --- [pool-2-thread-2] c.t.d.U.updateByPrimaryKeySelective : <== Updates: 1
2019-06-21 18:16:12.674 INFO 18368 --- [pool-2-thread-2] com.test.service.UserServiceTest : 结束修改数据
2019-06-21 18:16:12.682 INFO 18368 --- [ main] com.test.service.UserServiceTest : 测试完成
锁住的线程不添加,导致正常的修改无法执行,陷入阻塞,干扰线程执行完成,锁释放,正常的修改线程可以修改数据库信息,
扣减库存的场景
/**
* 悲观锁
*
* @param goodsId
* @param num
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void buy2(int goodsId, int num) {
//判断库存
GoodsDO goodsDO = goodsMapper.selectByPrimaryKeyLock(goodsId);
if (goodsDO.getStock() <= 0) {
logger.info("已经售完,id{}", goodsId);
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存
int newStock = goodsDO.getStock() - num;
int result = goodsMapper.updateStockById(goodsId, newStock);
if (result > 0) {
//提加订单
OrderDO orderDO = new OrderDO();
orderDO.setName(goodsDO.getName());
orderDO.setNum(num);
orderMapper.insertSelective(orderDO);
logger.info("购买成功,id{}", goodsId);
} else {
logger.info("购买失败");
}
}
上锁sql
<!-- 上锁-->
<select id="selectByPrimaryKeyLock" parameterType="java.lang.Integer" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_goods
where id = #{id,jdbcType=INTEGER}
for update
</select>
更新库存
<update id="updateStockById">
UPDATE t_goods
SET stock=#{newStock}
WHERE id=#{id}
</update>
2 乐观锁,安全性比悲观锁低,并发效率高。读的时候多的情况应该使用乐观锁。
/**
* 乐观锁
*/
@Test
public void testOptimisticLock(){
executorService.submit(()->{
int count=0;
while(count<10){
count++;
logger.info("正常线程,开始第{}次尝试修改数据",count);
User user = userDao.selectByPrimaryKey(1);
//模拟耗时操作
logger.info("正常线程,开始睡眠");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("正常线程,睡眠结束");
User updateUser=new User();
updateUser.setId(user.getId());
updateUser.setName("456");
logger.info("正常线程,开始尝试修改name字段");
if(userDao.updateUserByIdAndUuid(updateUser,user.getUuid())==1){
logger.info("正常线程,尝试修改name字段成功--------------");
break;
}else{
logger.info("正常线程,尝试修改name字段失败------------------");
}
}
});
executorService.submit(()->{
int count=0;
while(count<3){
count++;
User user=new User();
user.setId(1);
String uuid = UUID.randomUUID().toString().replaceAll("-", "");
user.setUuid(uuid);
logger.info("当前为搞破坏线程,第{}次修改UUID",count);
userDao.updateByPrimaryKeySelective(user);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
logger.info("当前为搞破坏线程,不再修改UUID");
});
executorService.shutdown();
while(true){
if(executorService.isTerminated()){
break;
}
}
logger.info("测试完成");
}
一个线程执行修改uuid字段,起到干扰的作用,正常的线程无法修改成功,干扰线程执行完成,正常线程执行完成。
执行结果:
2019-06-21 17:38:45.455 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始第1次尝试修改数据
2019-06-21 17:38:46.083 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : ==> Preparing: select id, uuid, `name`, `password` from `user` where id = ?
2019-06-21 17:38:46.102 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : ==> Parameters: 1(Integer)
2019-06-21 17:38:46.110 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : <== Total: 1
2019-06-21 17:38:46.111 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始睡眠
2019-06-21 17:38:46.163 INFO 10456 --- [pool-1-thread-2] com.test.service.UserServiceTest : 当前为搞破坏线程,第1次修改UUID
2019-06-21 17:38:46.196 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Preparing: update `user` SET uuid = ? where id = ?
2019-06-21 17:38:46.197 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Parameters: 1517bfcc69d2424683f8df72e47fa073(String), 1(Integer)
2019-06-21 17:38:46.228 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : <== Updates: 1
2019-06-21 17:38:48.228 INFO 10456 --- [pool-1-thread-2] com.test.service.UserServiceTest : 当前为搞破坏线程,第2次修改UUID
2019-06-21 17:38:48.228 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Preparing: update `user` SET uuid = ? where id = ?
2019-06-21 17:38:48.229 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Parameters: 22a5ca2858a54519ab269de3b0c55006(String), 1(Integer)
2019-06-21 17:38:48.278 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : <== Updates: 1
2019-06-21 17:38:50.279 INFO 10456 --- [pool-1-thread-2] com.test.service.UserServiceTest : 当前为搞破坏线程,第3次修改UUID
2019-06-21 17:38:50.280 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Preparing: update `user` SET uuid = ? where id = ?
2019-06-21 17:38:50.280 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : ==> Parameters: 305369d203324cf699f630596b494687(String), 1(Integer)
2019-06-21 17:38:50.339 DEBUG 10456 --- [pool-1-thread-2] c.t.d.U.updateByPrimaryKeySelective : <== Updates: 1
2019-06-21 17:38:51.112 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,睡眠结束
2019-06-21 17:38:51.112 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始尝试修改name字段
2019-06-21 17:38:51.118 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : ==> Preparing: update `user` SET `name` = ? where 1=1 AND id = ? AND uuid=?
2019-06-21 17:38:51.124 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : ==> Parameters: 456(String), 1(Integer), 5ca0168bfd8d470190c262584732d8d0(String)
2019-06-21 17:38:51.125 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : <== Updates: 0
2019-06-21 17:38:51.125 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,尝试修改name字段失败------------------
2019-06-21 17:38:51.125 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始第2次尝试修改数据
2019-06-21 17:38:51.125 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : ==> Preparing: select id, uuid, `name`, `password` from `user` where id = ?
2019-06-21 17:38:51.125 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : ==> Parameters: 1(Integer)
2019-06-21 17:38:51.126 DEBUG 10456 --- [pool-1-thread-1] com.test.dao.UserDao.selectByPrimaryKey : <== Total: 1
2019-06-21 17:38:51.127 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始睡眠
2019-06-21 17:38:52.339 INFO 10456 --- [pool-1-thread-2] com.test.service.UserServiceTest : 当前为搞破坏线程,不再修改UUID
2019-06-21 17:38:56.128 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,睡眠结束
2019-06-21 17:38:56.128 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,开始尝试修改name字段
2019-06-21 17:38:56.129 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : ==> Preparing: update `user` SET `name` = ? where 1=1 AND id = ? AND uuid=?
2019-06-21 17:38:56.129 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : ==> Parameters: 456(String), 1(Integer), 305369d203324cf699f630596b494687(String)
2019-06-21 17:38:56.223 DEBUG 10456 --- [pool-1-thread-1] c.t.dao.UserDao.updateUserByIdAndUuid : <== Updates: 1
2019-06-21 17:38:56.223 INFO 10456 --- [pool-1-thread-1] com.test.service.UserServiceTest : 正常线程,尝试修改name字段成功--------------
2019-06-21 17:38:56.224 INFO 10456 --- [ main] com.test.service.UserServiceTest : 测试完成
扣减库存场景-外层加重试机制
/**
* 乐观锁
*
* @param goodsId
* @param num
* @throws Exception
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void buy3(int goodsId, int num) throws Exception {
logger.info("----------------------开始测试---------------------------");
//判断库存
GoodsDO goodsDO = goodsMapper.selectByPrimaryKey(goodsId);
if (goodsDO.getStock() <= 0) {
logger.info("已经售完,id{}", goodsId);
return;
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存
int newStock = goodsDO.getStock() - num;
int result = goodsMapper.updateStockByIdAndVersion(goodsId, newStock, goodsDO.getVersion(), UUIDUtils.getUUID());
if (result > 0) {
//提加订单
OrderDO orderDO = new OrderDO();
orderDO.setName(goodsDO.getName());
orderDO.setNum(num);
orderMapper.insertSelective(orderDO);
logger.info("购买成功,id{}", goodsId);
} else {
logger.info("购买失败");
throw new Exception("购买失败");
}
}
加版本号更新
<update id="updateStockByIdAndVersion">
UPDATE t_goods
SET stock=#{newStock},
version=#{newVersion}
WHERE id=#{id}
AND version=#{oldVersion}
</update>
扣减库存当前读更新
/**
* mysql当前读
*
* @param goodsId
* @param num
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void buy1(int goodsId, int num) {
//判断库存
GoodsDO goodsDO = goodsMapper.selectByPrimaryKey(goodsId);
if (goodsDO.getStock() <= 0) {
logger.info("已经售完,id{}", goodsId);
return;
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//减库存
int result = goodsMapper.reduceStockById(goodsId, num);
if (result > 0) {
//提加订单
OrderDO orderDO = new OrderDO();
orderDO.setName(goodsDO.getName());
orderDO.setNum(num);
orderMapper.insertSelective(orderDO);
logger.info("购买成功,id{}", goodsId);
} else {
logger.info("购买失败");
}
}
更新,sql中对库存判断
<update id="reduceStockById">
UPDATE t_goods
SET stock=stock-#{num}
WHERE id=#{id}
AND stock>=#{num}
</update>
参考:https://mp.weixin.qq.com/s/ZJdfthAg2tImArzJO7bPaQ