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

Java基于注解實現(xiàn)的鎖實例解析

 更新時間:2020年02月27日 13:00:21   作者:min.jiang  
這篇文章主要介紹了Java基于注解實現(xiàn)的鎖實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下

背景

某些場景下,有可能一個方法不能被并發(fā)執(zhí)行,有可能一個方法的特定參數(shù)不能被并發(fā)執(zhí)行。比如不能將一個消息發(fā)送多次,創(chuàng)建緩存最好只創(chuàng)建一次等等。為了實現(xiàn)上面的目標(biāo)我們就需要采用同步機(jī)制來完成,但同步的邏輯如何實現(xiàn)呢,是否會影響到原有邏輯呢?

嵌入式

這里講的嵌入式是說獲取鎖以及釋放鎖的邏輯與業(yè)務(wù)代碼耦合在一起,又分分布式與單機(jī)兩種不同場景的不同實現(xiàn)。

單機(jī)版本

下面方法,每個productId不允許并發(fā)訪問,所以這里可以直接用synchronized來鎖定不同的參數(shù)。

@Service
public class ProductAppService {

  public void invoke(Integer productId) {
    synchronized (productId) {
      try {
        Thread.sleep(1000);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      System.out.print("productId:" + productId+" time:"+new Date());
    }
  }
}

測試腳本:三個相同的參數(shù)0,兩個不同的參數(shù)1和2,通過一個多線程的例子來模似。如果有并發(fā)請求的測試工具可能效果會更好。

private void testLock(){
  ExecutorService executorService= Executors.newFixedThreadPool(5);

  executorService.submit(new Runnable() {
    @Override
    public void run() {
      productAppService.invoke2(0);
    }
  });
  executorService.submit(new Runnable() {
    @Override
    public void run() {
      productAppService.invoke2(0);
    }
  });
  executorService.submit(new Runnable() {
    @Override
    public void run() {
      productAppService.invoke2(0);
    }
  });
  executorService.submit(new Runnable() {
    @Override
    public void run() {
      productAppService.invoke2(1);
    }
  });
  executorService.submit(new Runnable() {
    @Override
    public void run() {
      productAppService.invoke2(2);
    }
  });
  executorService.shutdown();
}

測試結(jié)果如下,0,1,2三個請求未被阻塞,后面的兩個0被阻塞。

分布式版本

分布式的除了鎖機(jī)制不同之外其它的測試方法相同,這里只貼出鎖的部分:

public void invoke2(Integer productId) {
  RLock lock=this.redissonService.getRedisson().getLock(productId.toString());
  try {
    boolean locked=lock.tryLock(3000,500, TimeUnit.MILLISECONDS);
    if(locked){
      Thread.sleep(1000);
      System.out.print("productId:" + productId+" time:"+new Date());
    }
  } catch (InterruptedException e) {
    e.printStackTrace();
  }
  finally {
    lock.unlock();
  }

}

嵌入式的缺點

比較明顯的就是鎖的邏輯與業(yè)務(wù)邏輯混合在一起,增加了程序復(fù)雜度而且也不利于鎖機(jī)制的更替。

注解式

能否將鎖的邏輯隱藏起來,通過在特定方法上增加注解來實現(xiàn)呢?就像Spring Cache的應(yīng)用。當(dāng)然是可以的,這里我們只需要解決如下三個問題:

定義注解

鎖一般有如下幾個屬性:

  • key,鎖對象的標(biāo)識,就是上面提到的方法的某些參數(shù)。一般由方法所屬類的完全限定名,方法名以及指定的參數(shù)構(gòu)成。
  • maximumWaiteTime,最大等待時間,避免線程死循環(huán)。
  • expirationTime,鎖的生命周期,可以有效避免因特殊原因未釋放鎖導(dǎo)致其它線程永遠(yuǎn)獲取不到鎖的局面。
  • timeUnit,配合上面兩個屬性使用,時間單位。
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface RequestLockable {

  String[] key() default "";

  long maximumWaiteTime() default 2000;

  long expirationTime() default 1000;

  TimeUnit timeUnit() default TimeUnit.MILLISECONDS;
}

實現(xiàn)注解

由于我們的目標(biāo)是注解式鎖,這里通過AOP的方式來實現(xiàn),具體依賴AspectJ,創(chuàng)建一個攔截器:

public abstract class AbstractRequestLockInterceptor {

  protected abstract Lock getLock(String key);

  protected abstract boolean tryLock(long waitTime, long leaseTime, TimeUnit unit,Lock lock) throws InterruptedException;

  /**
   * 包的表達(dá)式目前還有待優(yōu)化 TODO
   */
  @Pointcut("execution(* com.chanjet.csp..*(..)) && @annotation(com.chanjet.csp.product.core.annotation.RequestLockable)")
  public void pointcut(){}

  @Around("pointcut()")
  public Object doAround(ProceedingJoinPoint point) throws Throwable{
    Signature signature = point.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    Method method = methodSignature.getMethod();
    String targetName = point.getTarget().getClass().getName();
    String methodName = point.getSignature().getName();
    Object[] arguments = point.getArgs();

    if (method != null && method.isAnnotationPresent(RequestLockable.class)) {
      RequestLockable requestLockable = method.getAnnotation(RequestLockable.class);

      String requestLockKey = getLockKey(method,targetName, methodName, requestLockable.key(), arguments);
      Lock lock=this.getLock(requestLockKey);
      boolean isLock = this.tryLock(requestLockable.maximumWaiteTime(),requestLockable.expirationTime(), requestLockable.timeUnit(),lock);
      if(isLock) {
        try {
          return point.proceed();
        } finally {
          lock.unlock();
        }
      } else {
        throw new RuntimeException("獲取鎖資源失敗");
      }
    }

    return point.proceed();
  }

  private String getLockKey(Method method,String targetName, String methodName, String[] keys, Object[] arguments) {

    StringBuilder sb = new StringBuilder();
    sb.append("lock.").append(targetName).append(".").append(methodName);

    if(keys != null) {
      String keyStr = Joiner.on(".").skipNulls().join(keys);
      if(!StringUtils.isBlank(keyStr)) {
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameters =discoverer.getParameterNames(method);
        ExpressionParser parser = new SpelExpressionParser();
        Expression expression = parser.parseExpression(keyStr);
        EvaluationContext context = new StandardEvaluationContext();
        int length = parameters.length;
        if (length > 0) {
          for (int i = 0; i < length; i++) {
            context.setVariable(parameters[i], arguments[i]);
          }
        }
        String keysValue = expression.getValue(context, String.class);
        sb.append("#").append(keysValue);
      }
    }
    return sb.toString();
  }

}

注意如下幾點:

為什么會存在抽象方法?那是為下面的將注解機(jī)制與具體的鎖實現(xiàn)解耦服務(wù)的,目的是希望注解式鎖能夠得到復(fù)用也便于擴(kuò)展。鎖的key生成規(guī)則是什么?前綴一般是方法所在類的完全限定名,方法名稱以及spel表達(dá)式來構(gòu)成,避免重復(fù)。SPEL表達(dá)式如何支持?

LocalVariableTableParameterNameDiscoverer它在Spring MVC解析Controller的參數(shù)時有用到,可以從一個Method對象中獲取參數(shù)名稱列表。
SpelExpressionParser是標(biāo)準(zhǔn)的spel解析器,利用上面得來的參數(shù)名稱列表以及參數(shù)值列表來獲取真實表達(dá)式。

問題

基于aspectj的攔截器,@Pointcut中的參數(shù)目前未找到動態(tài)配置的方法,如果有解決方案的可以告訴我。

將注解機(jī)制與具體的鎖實現(xiàn)解耦

注解式鎖理論上應(yīng)該與具體的鎖實現(xiàn)細(xì)節(jié)分離,客戶端可以任意指定鎖,可以是單機(jī)下的ReentrantLock也可以是基于redis的分布式鎖,當(dāng)然也可以是基于zookeeper的鎖,基于此目的上面我們創(chuàng)建的AbstractRequestLockInterceptor這個攔截器是個抽象類??聪禄趓edis的分布式鎖的子類實現(xiàn):

@Aspect
public class RedisRequestLockInterceptor extends AbstractRequestLockInterceptor {

  @Autowired
  private RedissonService redissonService;

  private RedissonClient getRedissonClient(){
    return this.redissonService.getRedisson();
  }

  @Override
  protected Lock getLock(String key) {
    return this.getRedissonClient().getLock(key);
  }

  @Override
  protected boolean tryLock(long waitTime, long leaseTime, TimeUnit unit,Lock lock) throws InterruptedException {
    return ((RLock)lock).tryLock(waitTime,leaseTime,unit);
  }
}

注解式鎖的應(yīng)用

只需要在需要同步的方法上增加@RequestLockable,然后根據(jù)需要指定或者不指定key,也可以根據(jù)實際場景配置鎖等待時間以及鎖的生命周期。

  @RequestLockable(key = {"#productId"})
  public void invoke3(Integer productId) {
    try {
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.print("productId:" + productId+" time:"+new Date());
  }

當(dāng)然為了攔截器生效,我們需要在配置文件中配置上攔截器。

<bean class="com.product.api.interceptor.RedisRequestLockInterceptor"></bean>
<aop:aspectj-autoproxy proxy-target-class="true"/>

注解式鎖的優(yōu)點:鎖的邏輯與業(yè)務(wù)代碼完全分離,降低了復(fù)雜度。靈活的spel表達(dá)式可以靈活的構(gòu)建鎖的key。支持多種鎖,可以隨意切換而不影響業(yè)務(wù)代碼。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • 怎么把本地jar包放入本地maven倉庫和遠(yuǎn)程私服倉庫

    怎么把本地jar包放入本地maven倉庫和遠(yuǎn)程私服倉庫

    這篇文章主要介紹了怎么把本地jar包放入本地maven倉庫和遠(yuǎn)程私服倉庫的相關(guān)知識,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-06-06
  • Java?9?中的模塊Module系統(tǒng)

    Java?9?中的模塊Module系統(tǒng)

    Java?9?引入的模塊是在Java包(package)的基礎(chǔ)上又引入的一個新的抽象層,基于package這一點很重要,這里需要強(qiáng)調(diào)一下,接下來通過本文給大家介紹Java?9?中的模塊Module系統(tǒng),感興趣的朋友一起看看吧
    2022-03-03
  • Java如何通過線程解決生產(chǎn)者/消費者問題

    Java如何通過線程解決生產(chǎn)者/消費者問題

    這篇文章主要介紹了Java如何通過線程解決生產(chǎn)者/消費者問題,幫助大家更好的理解和使用Java,感興趣的朋友可以了解下
    2020-10-10
  • java教程之java繼承示例詳解

    java教程之java繼承示例詳解

    這篇文章主要介紹了java繼承示例詳解,需要的朋友可以參考下
    2014-04-04
  • Java Map.entry案例詳解

    Java Map.entry案例詳解

    這篇文章主要介紹了Java Map.entry案例詳解,本篇文章通過簡要的案例,講解了該項技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下
    2021-08-08
  • springboot集成Swagger的方法(讓你擁有屬于自己的api管理器)

    springboot集成Swagger的方法(讓你擁有屬于自己的api管理器)

    在大型的項目中,如果你有非常多的接口需要統(tǒng)一管理,或者需要進(jìn)行接口測試,那么我們通常會在繁雜地api中找到需要進(jìn)行測試或者管理的接口,接下來通過本文給大家介紹springboot集成Swagger的方法讓你擁有屬于自己的api管理器,感興趣的朋友一起看看吧
    2021-11-11
  • java自定義枚舉轉(zhuǎn)換器示例

    java自定義枚舉轉(zhuǎn)換器示例

    這篇文章主要介紹了java自定義枚舉轉(zhuǎn)換器示例,需要的朋友可以參考下
    2014-05-05
  • 一文了解Java中枚舉的使用

    一文了解Java中枚舉的使用

    Java中枚舉,大家在項目中經(jīng)常使用吧,主要用來定義一些固定值。那你了解枚舉的本質(zhì)嗎?了解枚舉的一些常見用法嗎?本文就來為大家一一進(jìn)行詳解
    2022-09-09
  • 在 Spring Boot 3 中接入生成式 AI的操作方法

    在 Spring Boot 3 中接入生成式 AI的操作方法

    本文介紹了如何在SpringBoot3中集成生成式AI,以O(shè)penAI的GPT模型為例,通過代碼示例展示了如何實現(xiàn),SpringBoot3的優(yōu)勢和OpenAI的生成式AI技術(shù)結(jié)合,為開發(fā)者提供了高效集成生成式AI的方法,感興趣的朋友跟隨小編一起看看吧
    2025-01-01
  • 細(xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)

    細(xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)

    下面小編就為大家?guī)硪黄?xì)數(shù)java中Long與Integer比較容易犯的錯誤總結(jié)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2017-01-01

最新評論

泽普县| 襄城县| 徐州市| 库尔勒市| 静宁县| 太仆寺旗| 铜山县| 南开区| 揭东县| 大足县| 汝阳县| 开远市| 大足县| 祁连县| 荆门市| 八宿县| 贵南县| 裕民县| 化德县| 青州市| 城口县| 永兴县| 岐山县| 乌兰县| 闸北区| 宁海县| 靖州| 郁南县| 平顶山市| 曲阳县| 宜川县| 阳曲县| 同仁县| 察隅县| 永平县| 临泽县| 福鼎市| 安庆市| 家居| 和龙市| 同江市|