欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

問題描述

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

輸出一下代碼:

@Override
@Transactional(rollbackFor = Exception.class)
public Result startSeckillLock(long seckillId, long userId) {
	/**
	  * 這里加鎖,還是會出現(xiàn)超賣
      *
      * 因為進(jìn)入service方法中時,spring事務(wù)已經(jīng)開啟,隔離級別默認(rèn)是可重復(fù)讀,
      * 因為事務(wù)先開啟,后加鎖,隔離級別為可重復(fù)讀的情況下,當(dāng)前線程讀不到其他線程更新的數(shù)據(jù),
      * 所以就會出現(xiàn)超賣的情況
      *
      * 下面方法通過aop加鎖,order = 1,在事務(wù)開啟之前加鎖
      *
      * 還有就是直接在controller中加鎖
      */
	lock.lock();
    try {
		//校驗庫存
        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){
            //扣庫存
            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 之后時,先執(zhí)行 finally 后,再執(zhí)行該 return;
//        finally 內(nèi)含有 return 時,直接執(zhí)行其 return 后結(jié)束;
//        finally 在 return 前,執(zhí)行完 finally 后再執(zhí)行 return。
//        return Result.ok(SeckillStatEnum.SUCCESS);
}

問題分析

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

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

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

因為是事務(wù)先開啟后加鎖,隔離級別為可重復(fù)讀的情況下,當(dāng)前線程是讀取不到其他線程更新的數(shù)據(jù),也就是說其他線程雖然更新了庫存且事務(wù)也提交了,但是因為當(dāng)前線程已經(jīng)開啟了事務(wù)(可重復(fù)讀的隔離級別),所以當(dāng)前線程在事務(wù)中獲取到的仍然是開啟事務(wù)時的庫存,所以就會出現(xià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) {
	//校驗庫存
    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){
        //扣庫存
        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);
    }
}

至此問題解決

表結(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 '商品庫存id',
  `name` varchar(120) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL COMMENT '商品名稱',
  `number` int(11) NOT NULL COMMENT '庫存數(shù)量',
  `start_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒殺開啟時間',
  `end_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '秒殺結(jié)束時間',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  `version` int(11) NOT NULL COMMENT '版本號',
  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 = '秒殺庫存表' 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指無效,0指成功,1指已付款',
  `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時間',
  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é)

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

相關(guān)文章

  • Graphics2D 寫圖片中文亂碼問題及解決

    Graphics2D 寫圖片中文亂碼問題及解決

    這篇文章主要介紹了Graphics2D 寫圖片中文亂碼問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • Java打亂數(shù)組元素簡單代碼例子

    Java打亂數(shù)組元素簡單代碼例子

    在Java編程中,我們經(jīng)常需要對數(shù)組進(jìn)行亂序操作(即將數(shù)組中的元素隨機(jī)打亂順序),這篇文章主要給大家介紹了關(guān)于Java打亂數(shù)組元素的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-03-03
  • java集合超詳細(xì)(最新推薦)

    java集合超詳細(xì)(最新推薦)

    在內(nèi)存中申請一塊空間用來存儲數(shù)據(jù),在Java中集合就是替換掉定長的數(shù)組的一種引用數(shù)據(jù)類型,本文介紹java集合超詳細(xì)講解,感興趣的朋友一起看看吧
    2024-12-12
  • SpringBoot集成IJPay實現(xiàn)微信v3支付的示例代碼

    SpringBoot集成IJPay實現(xiàn)微信v3支付的示例代碼

    本文主要介紹了SpringBoot集成IJPay實現(xiàn)微信v3支付的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-07-07
  • Java程序與C語言的區(qū)別淺析

    Java程序與C語言的區(qū)別淺析

    Java和C語言雖有相同性,但兩者也有一定的不同。Java程序是面向?qū)ο蟮囊环N簡單、分布式 、解釋、健壯、安全、結(jié)構(gòu)中立、可移植、高效能、多線程、動態(tài)的語言它是面向?qū)ο蠖鳦語言是面向過程的,這是最大的不同,對于學(xué)過C語言的我們來說,Java可以說是比較簡單的編程語言
    2017-04-04
  • springboot集成Deepseek4j的項目實踐

    springboot集成Deepseek4j的項目實踐

    本文主要介紹了springboot集成Deepseek4j的項目實踐,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-03-03
  • 詳解如何在SpringBoot中使用WebMvc

    詳解如何在SpringBoot中使用WebMvc

    Spring?Boot?是一個快速、簡單的開發(fā)框架,在?Spring?Boot?中,我們可以使用?WebMvc?來構(gòu)建?Web?應(yīng)用程序,所以本文就來講講如何在SpringBoot中使用WebMvc吧
    2023-06-06
  • SpringCloud基于RestTemplate微服務(wù)項目案例解析

    SpringCloud基于RestTemplate微服務(wù)項目案例解析

    這篇文章主要介紹了SpringCloud基于RestTemplate微服務(wù)項目案例,在寫SpringCloud搭建微服務(wù)之前,先搭建一個不通過springcloud只通過SpringBoot和Mybatis進(jìn)行模塊之間通訊,通過一個案例給大家詳細(xì)說明,需要的朋友可以參考下
    2022-05-05
  • java servlet手機(jī)app訪問接口(二)短信驗證

    java servlet手機(jī)app訪問接口(二)短信驗證

    這篇文章主要介紹了java servlet手機(jī)app訪問接口(二),短信驗證,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-12-12
  • jenkins如何部署應(yīng)用到多個環(huán)境

    jenkins如何部署應(yīng)用到多個環(huán)境

    本文介紹了如何基于流水線的方式將應(yīng)用程序部署到多個環(huán)境,包括測試環(huán)境和生產(chǎn)環(huán)境,通過創(chuàng)建項目、設(shè)置參數(shù)、配置流水線、設(shè)置環(huán)境變量、配置Maven工具、構(gòu)建階段、部署測試環(huán)境和生產(chǎn)環(huán)境、以及清理階段,實現(xiàn)了自動化部署流程
    2024-11-11

最新評論