避免同时执行两次Spring @Async任务

时间:2018-07-13 12:35:33

标签: java spring multithreading asynchronous concurrency

我只是在Spring Framework中学习多线程,我不知道如何处理一种情况。我的操作很持久,并且我不希望用户等待它完成,我发现有一个 @Async 批注将该方法标记为异步可执行。

我的问题是,阻止该方法以使同一公司的用户无法同时执行该方法的最佳方法是什么。准确无误,我想阻止甚至同一家公司的用户同时执行 analyzeData(...) anlyzeStatistics(...)

我正在考虑将ConcurrentHashMap与用户公司作为键,并将布尔值作为值,并在执行操作之前进行检查。我想知道我是朝着正确的方向前进,还是Spring提供了其他更合适的选择。

@Service
public class LongOperationService {

   @Async
   public void analyzeData(User user, List<String> data) {
       boolean operationResult = performLongOperation(data);
       if (opeartionResult) {
           log.info("Long operation ended successfully")
       } else {
           log.error("Long operation failure")
       }
   }

   @Async
   public void analyzeStatistics(User user, List<String> statistics) {
       ...
   }

   private void performLongOperation(List<String> data) {
        // Just for demonstration
        Thread.sleep(10000);
        return true;
   }
}

public class User {
   String username;
   String company;
}

2 个答案:

答案 0 :(得分:1)

您可以使用Semaphore来限制访问资源的线程数。

由于要阻止同一公司的用户同时访问您的分析功能,因此应为每个公司创建信号灯:

// Init on startup
// Key should be a unique identifier to a company, I assume the `String company` as key, you should adjust as your real requirement
static final Map<String, Semaphore> COMPANY_ENTRANT = new ConcurrentHashMap<>();
// for each company
COMPANY_ENTRANT.put(companyId, new Semaphore(1));

现在为您服务:

@Async
public void analyzeData(User user, List<String> data) {
   Semaphore entrant = COMPANY_ENTRANT.get(user.getCompany());
   try {
       entrant.acquire();
       try {
              boolean operationResult = performLongOperation(data);
              if (opeartionResult) {
                  log.info("Long operation ended successfully")
              } else {
                  log.error("Long operation failure")
              }
       } finally {
          entrant.release();
       }

   } catch(InterruptedException e) {
       ...
   }

}

如果要延迟COMPANY_ENTRANT映射的初始化,可以使用putIfAbsent

 Semaphore entrant = COMPANY_ENTRANT.putIfAbsent(user.getCompany(), new Semaphore(1));

答案 1 :(得分:1)

尝试这样的事情:

private final Set<String> runningOperations = Collections.synchronizedSet(new HashSet<>());
private final Object lock = new Object();

@Async
public void analyzeData(User user, List<String> data) throws Exception {
    synchronized (lock) {
        if (runningOperations.contains(user.company))
            return;
        runningOperations.add(user.company);
    }
    try {
        boolean operationResult = performLongOperation(data);
        if (operationResult) {
            log.info("Long operation ended successfully");
        } else {
            log.error("Long operation failure");
        }
    } finally {
        runningOperations.remove(user.company);
    }
}