悲观锁的代码实现如何编写?

TheDisguiser 2020-06-01 22:13:19 java常见问答 5530

悲观锁相信大家都不陌生了,之前说过了悲观锁的各种原理,这次我们就来聊聊悲观锁是如何实现的吧。

悲观锁,就跟它的名字意思一样,它非常悲观,它的机制就是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,所以,在全部的数据处理过程中,它会把数据处于锁定状态。悲观锁的实现,会依赖数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

一段执行逻辑加上悲观锁,不同线程同时执行时,只能有一个线程执行,其他的线程在入口处等待,直到锁被释放。

具体实现代码:

1)环境:mysql + jdbctemplate

2)商品表 goods:

DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`(
    `id`
    int(11) unsigned NOT NULL AUTO_INCREMENT
    , `name`
    varchar(100) DEFAULT NULL COMMENT '商品名称'
    , `stock`
    int(11) unsigned NOT NULL COMMENT '商品库存'
    , `version`
    int(2) DEFAULT NULL COMMENT '版本号'
    , `token_time`
    datetime NOT NULL COMMENT '乐观锁时间戳'
    , PRIMARY KEY(`id`)
) ENGINE = InnoDB AUTO_INCREMENT = 10 DEFAULT CHARSET = utf8;
-- -- -- -- -- -- -- -- -- -- -- -- -- -- --
--Records of goods
    -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
BEGIN;
INSERT INTO `goods`
VALUES(1, 'product', 9999, 1, '2018-11-30 22:06:20');
COMMIT;
SET FOREIGN_KEY_CHECKS = 1;

3)DAO层代码

package org.yugh.goodsstock.pessimistic_lock.repository;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
 * @author: YuGenHai
 * @name: SeckRepository
 * @creation: 2018/11/28 00:30
 * @notes: 悲观锁DAO层
 * @notes: 悲观锁需要注意mysql自带自动commit,用行锁需要开启事务 set transation 或者set autocommit =0
 * 防止自动提交,set autocommit =1 自动提交
 */
@Repository
public class PessimisticLockRepository
{
    /**
     * 测试使用 {@link JdbcTemplate}
     */
    @Resource
    private JdbcTemplate jdbcTemplate;
    /**
     * 获取现有库存量
     * @param id
     * @return
     * @author yugenhai
     */
    public int queryStock(long id)
    {
        //开启事务
        String lock = "set autocommit=0";
        jdbcTemplate.update(lock);
        //获得当前库存 并上锁
        String sql = "select * from goods where id=1 for update";
        List < Map < String, Object >> list = jdbcTemplate.queryForList(sql);
        if (null != list && list.size() > 0)
        {
            Map < String, Object > map = list.get(0);
            System.out.println("当前库存值: " + map.get("stock"));
            return Integer.valueOf(String.valueOf(map.get("stock")));
        }
        return 0;
    }
    /**
     * 还有库存量,并且要释放当前锁
     * @author yugenhai
     * @return
     */
    public int updateStock()
    {
        String update = "update goods set stock=stock-1 where id=1";
        jdbcTemplate.update(update);
        String unlock = "commit";
        jdbcTemplate.update(unlock);
        return 1;
    }
    /**
     * 商品被抢光后需要释放
     * @author yugenhai
     * @return
     */
    public int unlock()
    {
        String unlock = "commit";
        jdbcTemplate.update(unlock);
        return 1;
    }
}

4)悲观锁测试:

package org.yugh.goodsstock.pessimistic_lock;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.yugh.goodsstock.pessimistic_lock.repository.PessimisticLockRepository;
import javax.annotation.Resource;
/**
 * @author: YuGenHai
 * @name: PessimisticLockTest
 * @creation: 2018/11/28 00:32
 * @notes: 悲观锁测试秒杀商品
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class PessimisticLockTest
{
    @Resource
    PessimisticLockRepository pessimisticLockRepository;
    /**
     * STOCK库存总数,测试可以理解为购买者
     * 表里的stock对应库存
     */
    private static final int STOCK = 10000;
    /**
     * 悲观锁秒杀商品
     * @author yugenhai
     */
    @Test
    public void pessimisticLockTest()
    {
        long beTime = System.currentTimeMillis();
        for (int i = 0; i < STOCK; i++)
        {
            //获得当前库存
            //顺带上锁,开启事务
            int stock = pessimisticLockRepository.queryStock(1);
            if (stock > 0)
            {
                //库存还有
                //当前用户继续秒杀一个商品 并提交事务 释放锁
                pessimisticLockRepository.updateStock();
                System.out.println(new Thread()
                    .getName() + " 抢到了第 " + (i + 1) + " 商品");
            }
            else
            {
                //没有库存后释放锁
                System.err.println(new Thread()
                    .getName() + " 抱歉,商品没有库存了!");
                pessimisticLockRepository.unlock();
                //break;
            }
        }
        System.out.println("秒杀 " + STOCK + " 件商品使用悲观锁需要花费时间:" + (System.currentTimeMillis() - beTime));
    }
}

5)模拟一万用户在抢购,最后只有一位用户没有抢到:

当前库存值: 8
Thread - 9994 抢到了第 9992 商品
当前库存值: 7
Thread - 9995 抢到了第 9993 商品
当前库存值: 6
Thread - 9996 抢到了第 9994 商品
当前库存值: 5
Thread - 9997 抢到了第 9995 商品
当前库存值: 4
Thread - 9998 抢到了第 9996 商品
当前库存值: 3
Thread - 9999 抢到了第 9997 商品
当前库存值: 2
Thread - 10000 抢到了第 9998 商品
当前库存值: 1
Thread - 10001 抢到了第 9999 商品
当前库存值: 0
秒杀 10000 件商品使用悲观锁需要花费时间: 9922
Thread - 10002 抱歉, 商品没有库存了!
2018 - 12 - 01 00: 51: 06.914 INFO 9125-- - [Thread - 2] s.c.a.AnnotationConfigApplicationContext: Closing org.springframework.context.annotation.AnnotationConfigApplicationContext @f0da945: startup date[Sat Dec 01 00: 50: 56 CST 2018];
root of context hierarchy
2018 - 12 - 01 00: 51: 06.915 INFO 9125-- - [Thread - 2] com.zaxxer.hikari.HikariDataSource: HikariPool - 1 - Shutdown initiated...
    2018 - 12 - 01 00: 51: 06.920 INFO 9125-- - [Thread - 2] com.zaxxer.hikari.HikariDataSource: HikariPool - 1 - Shutdown completed.

以上就是今天的全部内容了,悲观锁一般适合写入操作比较频繁的场景;如若想要了解更多锁机制相关java常见问答知识,烦请持续关注我们的网站吧。

推荐阅读:

悲观锁和排他锁有哪些基础概念?该怎么实现?

乐观锁和悲观锁的区别有什么?有什么作用?

乐观锁和悲观锁详解,具体概念介绍