最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java并發(fā)編程service層處理并發(fā)事務(wù)加鎖可能會(huì)無(wú)效問(wèn)題

 更新時(shí)間:2023年07月27日 09:43:58   作者:煙雨樓臺(tái)笑江湖  
這篇文章主要介紹了Java并發(fā)編程service層處理并發(fā)事務(wù)加鎖可能會(huì)無(wú)效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

問(wèn)題描述

近期寫了一個(gè)單體架構(gòu)秒殺的功能,在對(duì)商品庫(kù)存進(jìn)行扣減,有線程安全問(wèn)題,因此加了Lock鎖進(jìn)行同步,但發(fā)現(xiàn)加鎖后并沒有控制住庫(kù)存線程安全的問(wèn)題,導(dǎo)致庫(kù)存仍被超發(fā)。

輸出一下代碼:

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSeckillLock(long seckillId, long userId) {
	/**
	  * 這里加鎖,還是會(huì)出現(xiàn)超賣
      *
      * 因?yàn)檫M(jìn)入service方法中時(shí),spring事務(wù)已經(jīng)開啟,隔離級(jí)別默認(rèn)是可重復(fù)讀,
      * 因?yàn)槭聞?wù)先開啟,后加鎖,隔離級(jí)別為可重復(fù)讀的情況下,當(dāng)前線程讀不到其他線程更新的數(shù)據(jù),
      * 所以就會(huì)出現(xiàn)超賣的情況
      *
      * 下面方法通過(guò)aop加鎖,order = 1,在事務(wù)開啟之前加鎖
      *
      * 還有就是直接在controller中加鎖
      */
	lock.lock();
    try {
		//校驗(yàn)庫(kù)存
        String nativeSql = "SELECT number FROM seckill WHERE seckill_id = ?";
        Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
        Long number =  ((Number) object).longValue();
        System.out.println(">>>>>>>>>>>>>>>>>>>>>> number : {}" + number);
        if(number > 0){
            //扣庫(kù)存
            nativeSql = "UPDATE seckill  SET number=? WHERE seckill_id = ?";
            dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number - 1, seckillId});
            //創(chuàng)建訂單
            SuccessKilled killed = new SuccessKilled();
            killed.setSeckillId(seckillId);
            killed.setUserId(userId);
            killed.setState((short)0);
            killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
            dynamicQuery.save(killed);
            return Result.ok(SeckillStatEnum.SUCCESS);
            //支付
        }else{
            return Result.error(SeckillStatEnum.END);
        }
    } finally {
        lock.unlock();
    }
//        https://cloud.tencent.com/developer/article/1630866
//        finally 在 return 之后時(shí),先執(zhí)行 finally 后,再執(zhí)行該 return;
//        finally 內(nèi)含有 return 時(shí),直接執(zhí)行其 return 后結(jié)束;
//        finally 在 return 前,執(zhí)行完 finally 后再執(zhí)行 return。
//        return Result.ok(SeckillStatEnum.SUCCESS);
}

問(wèn)題分析

由于spring事務(wù)是通過(guò)AOP實(shí)現(xiàn)的,所以在startSeckillLock()方法執(zhí)行之前會(huì)開啟事務(wù),之后會(huì)有提交事務(wù)的邏輯。

而lock的動(dòng)作是發(fā)生在事務(wù)之內(nèi)。

數(shù)據(jù)庫(kù)默認(rèn)的事務(wù)隔離級(jí)別為可重復(fù)讀(repeatable-read)。

因?yàn)槭鞘聞?wù)先開啟后加鎖,隔離級(jí)別為可重復(fù)讀的情況下,當(dāng)前線程是讀取不到其他線程更新的數(shù)據(jù),也就是說(shuō)其他線程雖然更新了庫(kù)存且事務(wù)也提交了,但是因?yàn)楫?dāng)前線程已經(jīng)開啟了事務(wù)(可重復(fù)讀的隔離級(jí)別),所以當(dāng)前線程在事務(wù)中獲取到的仍然是開啟事務(wù)時(shí)的庫(kù)存,所以就會(huì)出現(xiàn)超賣的情況。

問(wèn)題解決

一:在controller層加鎖

二:在service層自己定義事務(wù)的開啟和提交,加鎖的代碼方到開啟事務(wù)之前,解鎖在提交事務(wù)之后

三:AOP+鎖

自定義注解ServiceLock:

@Target({ElementType.PARAMETER, ElementType.METHOD})    
@Retention(RetentionPolicy.RUNTIME)    
@Documented    
public  @interface Servicelock { 
     String description()  default "";
}

自定義切面LockAspect:

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@Component
@Aspect
@Order(1)
public class LockAspect {
    private static final Lock lock = new ReentrantLock();
    @Pointcut("@annotation(com.wjy.seckill.common.aop.ServiceLock)")
    public void lockAspect() {
    }
    @Around("lockAspect()")
    public Object around(ProceedingJoinPoint joinPoint) {
        lock.lock();
        Object result = null;
        try {
            result = joinPoint.proceed();
        } catch (Throwable e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
        return result;
    }
}

切入秒殺方法:

@Override
@ServiceLock
@Transactional(rollbackFor = Exception.class)
public Result startSeckillAopLock(long seckillId, long userId) {
	//校驗(yàn)庫(kù)存
    String nativeSql = "SELECT number FROM seckill WHERE seckill_id = ?";
    Object object =  dynamicQuery.nativeQueryObject(nativeSql, new Object[]{seckillId});
    Long number =  ((Number) object).longValue();
    System.out.println(">>>>>>>>>>>>>>>>>>>>>> number : {}" + number);
    if(number > 0){
        //扣庫(kù)存
        nativeSql = "UPDATE seckill  SET number=? WHERE seckill_id = ?";
        dynamicQuery.nativeExecuteUpdate(nativeSql, new Object[]{number - 1, seckillId});
        //創(chuàng)建訂單
        SuccessKilled killed = new SuccessKilled();
        killed.setSeckillId(seckillId);
        killed.setUserId(userId);
        killed.setState((short)0);
        killed.setCreateTime(new Timestamp(System.currentTimeMillis()));
        dynamicQuery.save(killed);
        return Result.ok(SeckillStatEnum.SUCCESS);
        //支付
    }else{
    	return Result.error(SeckillStatEnum.END);
    }
}

至此問(wèn)題解決

表結(jié)構(gòu)

/*
 Navicat Premium Data Transfer
 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50732
 Source Host           : localhost:3306
 Source Schema         : spring-boot-seckill
 Target Server Type    : MySQL
 Target Server Version : 50732
 File Encoding         : 65001
 Date: 05/01/2022 15:51:06
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for seckill
-- ----------------------------
DROP TABLE IF EXISTS `seckill`;
CREATE TABLE `seckill`  (
  `seckill_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '商品庫(kù)存id',
  `name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名稱',
  `number` int(11) NOT NULL COMMENT '庫(kù)存數(shù)量',
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒殺開啟時(shí)間',
  `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒殺結(jié)束時(shí)間',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
  `version` int(11) NOT NULL COMMENT '版本號(hào)',
  PRIMARY KEY (`seckill_id`) USING BTREE,
  INDEX `idx_start_time`(`start_time`) USING BTREE,
  INDEX `idx_end_time`(`end_time`) USING BTREE,
  INDEX `idx_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1004 CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '秒殺庫(kù)存表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of seckill
-- ----------------------------
INSERT INTO `seckill` VALUES (1000, '1000元秒殺iphone8', 100, '2018-05-10 15:31:53', '2018-05-10 15:31:53', '2018-05-10 15:31:53', 0);
INSERT INTO `seckill` VALUES (1001, '500元秒殺ipad2', 100, '2018-05-10 15:31:53', '2018-05-10 15:31:53', '2018-05-10 15:31:53', 0);
INSERT INTO `seckill` VALUES (1002, '300元秒殺小米4', 100, '2018-05-10 15:31:53', '2018-05-10 15:31:53', '2018-05-10 15:31:53', 0);
INSERT INTO `seckill` VALUES (1003, '200元秒殺紅米note', 100, '2018-05-10 15:31:53', '2018-05-10 15:31:53', '2018-05-10 15:31:53', 0);
-- ----------------------------
-- Table structure for success_killed
-- ----------------------------
DROP TABLE IF EXISTS `success_killed`;
CREATE TABLE `success_killed`  (
  `seckill_id` bigint(20) NOT NULL COMMENT '秒殺商品id',
  `user_id` bigint(20) NOT NULL COMMENT '用戶Id',
  `state` tinyint(4) NOT NULL COMMENT '狀態(tài)標(biāo)示:-1指無(wú)效,0指成功,1指已付款',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間',
  PRIMARY KEY (`seckill_id`, `user_id`) USING BTREE,
  INDEX `idx_create_time`(`create_time`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_general_ci COMMENT = '秒殺成功明細(xì)表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of success_killed
-- ----------------------------
SET FOREIGN_KEY_CHECKS = 1;

總結(jié)

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇

    一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇

    最近學(xué)習(xí)了spring相關(guān)知識(shí),公司項(xiàng)目也用到了spring,下面這篇文章主要給大家介紹了一次Spring無(wú)法啟動(dòng)的問(wèn)題排查實(shí)戰(zhàn)之字節(jié)碼篇的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-04-04
  • Java數(shù)據(jù)結(jié)構(gòu)之鏈表實(shí)現(xiàn)(單向、雙向鏈表及鏈表反轉(zhuǎn))

    Java數(shù)據(jù)結(jié)構(gòu)之鏈表實(shí)現(xiàn)(單向、雙向鏈表及鏈表反轉(zhuǎn))

    這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)結(jié)構(gòu)之鏈表實(shí)現(xiàn)的相關(guān)資料,其中包括單向鏈表、雙向鏈表及鏈表反轉(zhuǎn)的實(shí)現(xiàn)代碼,需要的朋友可以參考下
    2021-06-06
  • Spring?Boot如何配置yml配置文件定義集合、數(shù)組和Map

    Spring?Boot如何配置yml配置文件定義集合、數(shù)組和Map

    這篇文章主要介紹了Spring?Boot?優(yōu)雅配置yml配置文件定義集合、數(shù)組和Map,包括Spring?Boot?yml配置文件定義基本數(shù)據(jù)類型和引用數(shù)據(jù)類型的方式,需要的朋友可以參考下
    2023-10-10
  • Java 數(shù)據(jù)結(jié)構(gòu)進(jìn)階二叉樹題集上

    Java 數(shù)據(jù)結(jié)構(gòu)進(jìn)階二叉樹題集上

    二叉樹可以簡(jiǎn)單理解為對(duì)于一個(gè)節(jié)點(diǎn)來(lái)說(shuō),最多擁有一個(gè)上級(jí)節(jié)點(diǎn),同時(shí)最多具備左右兩個(gè)下級(jí)節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)。本文將帶你通過(guò)實(shí)際題目來(lái)熟練掌握
    2022-04-04
  • Java computeIfAbsent()方法使用小結(jié)

    Java computeIfAbsent()方法使用小結(jié)

    本文主要介紹了Java computeIfAbsent()方法使用小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2025-12-12
  • SpringMVC攔截器失效問(wèn)題及解決方法

    SpringMVC攔截器失效問(wèn)題及解決方法

    這篇文章主要介紹了SpringMVC?攔截器和異常處理器,以及SpringMVC攔截器失效分析解決方案,文中補(bǔ)充介紹了SpringMVC?攔截器和異常處理器的相關(guān)知識(shí),需要的朋友可以參考下
    2024-01-01
  • 詳解在Java中如何創(chuàng)建多線程程序

    詳解在Java中如何創(chuàng)建多線程程序

    今天給大家?guī)?lái)的是關(guān)于Java的相關(guān)知識(shí),文章圍繞著在Java中如何創(chuàng)建多線程程序展開,文中有非常詳細(xì)的介紹及代碼示例,需要的朋友可以參考下
    2021-06-06
  • JAVA語(yǔ)法糖原理你知道嗎

    JAVA語(yǔ)法糖原理你知道嗎

    語(yǔ)法糖(Syntactic sugar),也叫做糖衣語(yǔ)法,是英國(guó)科學(xué)家發(fā)明的一個(gè)術(shù)語(yǔ),通常來(lái)說(shuō)使用語(yǔ)法糖能夠增加程序的可讀性,從而減少程序代碼出錯(cuò)的機(jī)會(huì).這篇文章主要介紹了Java 中的語(yǔ)法糖知識(shí),需要的朋友可以參考下
    2021-09-09
  • springboot 事件監(jiān)聽的實(shí)現(xiàn)方法

    springboot 事件監(jiān)聽的實(shí)現(xiàn)方法

    這篇文章主要介紹了springboot 事件監(jiān)聽的實(shí)現(xiàn)方法,并詳細(xì)的介紹了四種監(jiān)聽方式,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2019-04-04
  • 詳解Java中NullPointerException異常的原因詳解以及解決方法

    詳解Java中NullPointerException異常的原因詳解以及解決方法

    這篇文章主要介紹了詳解Java中NullPointerException異常的原因詳解以及解決方法。文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08

最新評(píng)論

邢台市| 临海市| 辉县市| 左贡县| 灵石县| 玛沁县| 修文县| 逊克县| 桐梓县| 新疆| 苗栗县| 岱山县| 溆浦县| 屏东市| 渭南市| 湟中县| 泸水县| 织金县| 中宁县| 苗栗市| 泾源县| 天等县| 酒泉市| 桂阳县| 永嘉县| 井陉县| 昌宁县| 漳州市| 格尔木市| 桐城市| 沽源县| 玉林市| 平舆县| 巴林右旗| 潞城市| 乡城县| 海淀区| 桐柏县| 拜城县| 通州区| 衡南县|