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

Spring中使用自定義@Lock 注解解決線程并發(fā)的核心思路

 更新時間:2026年03月12日 09:38:34   作者:xiaoye3708  
本文介紹了如何在Spring生態(tài)中使用自定義的@Lock注解結(jié)合AOP切面來實現(xiàn)本地鎖和分布式鎖,以解決多線程并發(fā)問題,感興趣的朋友跟隨小編一起看看吧

在 Spring 生態(tài)中,并沒有原生的 @Lock 注解,但業(yè)界常用的做法是自定義 @Lock 注解 + AOP 切面 來封裝鎖邏輯(支持本地鎖 / 分布式鎖),讓代碼更簡潔、可復用。下面我會完整講解如何定義和使用 @Lock 注解解決線程并發(fā)問題,包括本地鎖版(單實例)和分布式鎖版(多實例)兩種核心場景。

一、核心思路

@Lock 注解的本質(zhì)是通過 AOP 攔截標注了該注解的方法,在方法執(zhí)行前加鎖、執(zhí)行后釋放鎖,從而保證方法執(zhí)行的原子性,解決多線程競爭共享資源的問題。

二、場景 1:單實例下的 @Lock(本地鎖)

適用于單服務器部署的應用,使用 ReentrantLock(可重入鎖)實現(xiàn)。

步驟 1:定義 @Lock 注解

import java.lang.annotation.*;
import java.util.concurrent.TimeUnit;
/**
 * 自定義鎖注解(本地鎖)
 * 用于標記需要加鎖的方法,解決單實例并發(fā)問題
 */
@Target(ElementType.METHOD) // 僅作用于方法
@Retention(RetentionPolicy.RUNTIME) // 運行時生效
@Documented
public @interface Lock {
    /**
     * 鎖的唯一標識(支持SpEL表達式,如 "#userId")
     * 為空時默認使用「類名.方法名」作為鎖標識
     */
    String key() default "";
    /**
     * 加鎖超時時間(默認5秒)
     * 超時未獲取鎖則拋出異常,避免線程阻塞
     */
    long waitTime() default 5;
    /**
     * 鎖的自動釋放時間(默認30秒)
     * 防止死鎖(ReentrantLock本身不會死鎖,但超時釋放更安全)
     */
    long leaseTime() default 30;
    /**
     * 時間單位(默認秒)
     */
    TimeUnit timeUnit() default TimeUnit.SECONDS;
}

步驟 2:實現(xiàn) @Lock 注解的 AOP 切面(核心)

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @Lock注解的切面實現(xiàn)(本地鎖)
 */
@Aspect // 標記為AOP切面
@Component // 交給Spring容器管理
public class LocalLockAspect {
    // 緩存鎖實例:key=鎖標識,value=ReentrantLock對象(ConcurrentHashMap保證線程安全)
    private final Map<String, Lock> lockCache = new ConcurrentHashMap<>();
    // SpEL表達式解析器(解析注解中的key參數(shù))
    private final ExpressionParser spelParser = new SpelExpressionParser();
    // 參數(shù)名解析器(獲取方法參數(shù)名,用于SpEL解析)
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    /**
     * 環(huán)繞通知:攔截所有標注了@Lock的方法
     */
    @Around("@annotation(lock)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        // 1. 解析鎖的唯一標識(key)
        String lockKey = resolveLockKey(joinPoint, lock);
        // 2. 獲取或創(chuàng)建鎖(不存在則新建,存在則復用)
        Lock reentrantLock = lockCache.computeIfAbsent(lockKey, k -> new ReentrantLock());
        // 3. 加鎖(支持超時等待,避免線程無限阻塞)
        boolean locked = false;
        try {
            locked = ((java.util.concurrent.locks.Lock) reentrantLock).tryLock(
                    lock.waitTime(),
                    lock.leaseTime(),
                    lock.timeUnit()
            );
            if (!locked) {
                throw new RuntimeException("獲取鎖失敗,請稍后重試");
            }
            // 4. 執(zhí)行原方法(核心業(yè)務邏輯)
            return joinPoint.proceed();
        } finally {
            // 5. 釋放鎖(僅當前線程持有鎖時釋放,避免誤釋放)
            if (locked && reentrantLock.isHeldByCurrentThread()) {
                reentrantLock.unlock();
            }
        }
    }
    /**
     * 解析@Lock注解中的key(支持SpEL表達式)
     */
    private String resolveLockKey(ProceedingJoinPoint joinPoint, Lock lock) {
        // 如果注解key為空,默認使用「類名.方法名」作為鎖標識
        if (lock.key().isEmpty()) {
            return joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        }
        // 解析SpEL表達式(如 "#userId" 解析為實際的用戶ID)
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return spelParser.parseExpression(lock.key()).getValue(context, String.class);
    }
}

步驟 3:使用 @Lock 注解解決并發(fā)問題

以 “庫存扣減” 這個典型并發(fā)場景為例:

import org.springframework.stereotype.Service;
@Service
public class StockService {
    // 共享資源:模擬庫存(多線程競爭的核心)
    private int stock = 100;
    /**
     * 扣減庫存(并發(fā)場景)
     * @param productId 商品ID(按商品ID加鎖,不同商品不互斥,提高并發(fā)效率)
     * @param num 扣減數(shù)量
     */
    @Lock(key = "'stock:' + #productId") // SpEL表達式:鎖標識為 "stock:商品ID"
    public void deductStock(Long productId, int num) {
        if (stock >= num) {
            // 模擬耗時操作(如數(shù)據(jù)庫查詢/更新)
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
            stock -= num;
            System.out.printf("商品%s扣減庫存%d,剩余庫存:%d%n", productId, num, stock);
        } else {
            throw new RuntimeException("商品" + productId + "庫存不足");
        }
    }
    // 獲取庫存(測試用)
    public int getStock() {
        return stock;
    }
}

步驟 4:測試并發(fā)效果

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class LockTest {
    public static void main(String[] args) {
        // 初始化Spring容器(掃描注解所在包)
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext("com.example");
        StockService stockService = context.getBean(StockService.class);
        // 模擬10個線程并發(fā)扣減同一商品(productId=1)的庫存(每個扣10)
        for (int i = 0; i < 10; i++) {
            new Thread(() -> stockService.deductStock(1L, 10)).start();
        }
    }
}

測試結(jié)果(無并發(fā)問題,庫存最終為 0):

商品1扣減庫存10,剩余庫存:90
商品1扣減庫存10,剩余庫存:80
商品1扣減庫存10,剩余庫存:70
...
商品1扣減庫存10,剩余庫存:0

三、場景 2:分布式下的 @Lock(分布式鎖)

如果應用部署在多臺服務器(多實例),本地鎖會失效,需要將 @Lock 注解適配為分布式鎖(基于 Redis/Redisson)。

步驟 1:引入 Redisson 依賴(Maven)

<!-- Redisson:Redis分布式鎖的最佳實踐 -->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.23.3</version>
</dependency>

步驟 2:修改 @Lock 切面為分布式鎖實現(xiàn)

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
/**
 * @Lock注解的切面實現(xiàn)(分布式鎖)
 */
@Aspect
@Component
public class DistributedLockAspect {
    @Autowired
    private RedissonClient redissonClient; // 注入Redisson客戶端(SpringBoot自動配置)
    private final ExpressionParser spelParser = new SpelExpressionParser();
    private final DefaultParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    @Around("@annotation(lock)")
    public Object around(ProceedingJoinPoint joinPoint, Lock lock) throws Throwable {
        // 1. 解析鎖標識
        String lockKey = resolveLockKey(joinPoint, lock);
        // 2. 獲取分布式鎖
        RLock redissonLock = redissonClient.getLock(lockKey);
        // 3. 加鎖
        boolean locked = false;
        try {
            locked = redissonLock.tryLock(
                    lock.waitTime(),
                    lock.leaseTime(),
                    lock.timeUnit()
            );
            if (!locked) {
                throw new RuntimeException("獲取分布式鎖失敗,請稍后重試");
            }
            // 4. 執(zhí)行業(yè)務方法
            return joinPoint.proceed();
        } finally {
            // 5. 釋放鎖(僅當前線程持有鎖時釋放)
            if (locked && redissonLock.isHeldByCurrentThread()) {
                redissonLock.unlock();
            }
        }
    }
    // 復用之前的resolveLockKey方法(解析SpEL)
    private String resolveLockKey(ProceedingJoinPoint joinPoint, Lock lock) {
        if (lock.key().isEmpty()) {
            return joinPoint.getTarget().getClass().getName() + "." + joinPoint.getSignature().getName();
        }
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        String[] paramNames = parameterNameDiscoverer.getParameterNames(method);
        Object[] args = joinPoint.getArgs();
        StandardEvaluationContext context = new StandardEvaluationContext();
        for (int i = 0; i < paramNames.length; i++) {
            context.setVariable(paramNames[i], args[i]);
        }
        return spelParser.parseExpression(lock.key()).getValue(context, String.class);
    }
}

步驟 3:分布式場景下使用 @Lock

用法和本地鎖完全一致,無需修改業(yè)務代碼:

@Service
public class OrderService {
    /**
     * 提交訂單(分布式并發(fā)場景)
     * @param orderId 訂單ID(按訂單ID加鎖,不同訂單不互斥)
     */
    @Lock(key = "'order:' + #orderId", leaseTime = 10) // 鎖過期時間10秒
    public void submitOrder(String orderId) {
        // 分布式并發(fā)操作:扣減庫存、生成訂單記錄
        System.out.println("訂單" + orderId + "提交成功");
    }
}

四、關鍵注意事項

  1. 鎖粒度要合理
    • 避免使用全局鎖(如固定 key),否則會導致所有請求串行,降低并發(fā)效率;
    • 建議按業(yè)務維度加鎖(如商品 ID、訂單 ID),僅讓同一業(yè)務維度的請求互斥。
  2. 防止死鎖
    • 必須在 finally 塊中釋放鎖,確保無論方法是否異常,鎖都會釋放;
    • 分布式鎖必須設置 leaseTime(自動過期時間),防止服務宕機導致鎖無法釋放。
  3. SpEL 表達式規(guī)范
    • 字符串常量需要用單引號包裹(如 'stock:' + #productId),否則會解析失敗。

總結(jié)

  1. @Lock 注解是通過 AOP 切面 + 鎖機制 實現(xiàn)的 “語法糖”,核心是在方法執(zhí)行前后加鎖 / 釋放鎖,保證原子性;
  2. 單實例場景下,@Lock 適配 ReentrantLock(本地鎖),多實例場景下適配 Redisson(分布式鎖),業(yè)務代碼無需修改;
  3. 使用 @Lock 時需注意鎖粒度(按業(yè)務參數(shù)加鎖)和防死鎖(finally 釋放、設置過期時間),這是解決并發(fā)問題的關鍵。

到此這篇關于Spring中使用自定義@Lock 注解解決線程并發(fā)的核心思路的文章就介紹到這了,更多相關Spring使用自定義@Lock 注解內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java實現(xiàn)商城訂單超時取消功能

    Java實現(xiàn)商城訂單超時取消功能

    大多數(shù)的B2C商城項目都會有限時活動,當用戶下單后都會有支付超時時間,當訂單超時后訂單的狀態(tài)就會自動變成已取消 ,這個功能的實現(xiàn)有很多種方法,本文的實現(xiàn)方法適合大多數(shù)比較小的商城使用。具體實現(xiàn)方式可以跟隨小編一起看看吧
    2019-12-12
  • Java 5億整數(shù)大文件怎么排序

    Java 5億整數(shù)大文件怎么排序

    這篇文章主要介紹了Java 5億整數(shù)大文件怎么排序,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-03-03
  • Spring AOP實現(xiàn)斷路器方式

    Spring AOP實現(xiàn)斷路器方式

    這篇文章主要介紹了Spring AOP實現(xiàn)斷路器方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-05-05
  • 詳解Redisson分布式限流的使用及原理

    詳解Redisson分布式限流的使用及原理

    本文介紹了Redisson分布式限流的使用方法和原理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2025-02-02
  • Spring中Feign的調(diào)用流程詳解

    Spring中Feign的調(diào)用流程詳解

    這篇文章主要介紹了Spring中Feign的調(diào)用流程詳解,分析過了創(chuàng)建的代理是FeignInvocationHandler,那我們就打斷點,停在它的反射方法上,看看到底做了什么,需要的朋友可以參考下
    2023-11-11
  • Java如何實現(xiàn)List自定義排序

    Java如何實現(xiàn)List自定義排序

    這篇文章主要介紹了Java如何實現(xiàn)List自定義排序,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-09-09
  • Spring的@Autowired加到接口上但獲取的是實現(xiàn)類的問題

    Spring的@Autowired加到接口上但獲取的是實現(xiàn)類的問題

    這篇文章主要介紹了Spring的@Autowired加到接口上但獲取的是實現(xiàn)類的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • springboot訪問template下的html頁面的實現(xiàn)配置

    springboot訪問template下的html頁面的實現(xiàn)配置

    這篇文章主要介紹了springboot訪問template下的html頁面的實現(xiàn)配置,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-12-12
  • SpringBoot集成elasticsearch使用圖文詳解

    SpringBoot集成elasticsearch使用圖文詳解

    Spring Boot集成Elasticsearch其實非常簡單,這篇文章主要給大家介紹了關于SpringBoot集成elasticsearch使用的相關資料,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下
    2023-04-04
  • java 實現(xiàn)文件夾的拷貝實例代碼

    java 實現(xiàn)文件夾的拷貝實例代碼

    這篇文章主要介紹了java 實現(xiàn)文件夾的拷貝實例代碼的相關資料,需要的朋友可以參考下
    2017-04-04

最新評論

弥渡县| 翁牛特旗| 建始县| 军事| 扶沟县| 黔西县| 长白| 佳木斯市| 新泰市| 两当县| 龙川县| 安丘市| 玉树县| 肇东市| 鄂托克前旗| 清镇市| 台北市| 柯坪县| 宁阳县| 五莲县| 太原市| 合山市| 普兰店市| 铁岭县| 长沙县| 清原| 嫩江县| 金阳县| 鄂托克旗| 罗平县| 肃宁县| 南靖县| 格尔木市| 家居| 视频| 宁强县| 香格里拉县| 祥云县| 赣州市| 平乡县| 宜州市|