Избегайте выполнения задачи Spring @Async дважды в одно и то же время

Вот логика. предположим, что мы хотим запустить калькулятор в окнах с помощью java:

Process p = Runtime.getRuntime().exec(System.getenv("windir") + "\\system32\\" + "calc.exe");
3
задан Sun 13 July 2018 в 12:45
поделиться

2 ответа

Попробуйте что-то вроде этого:

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);
    }
}
0
ответ дан alaster 17 August 2018 в 12:51
поделиться
  • 1
    Является ли эта нить безопасной? – grape_mao 13 July 2018 в 13:02
  • 2
    Просто отредактирован, так что это потокобезопасно, я думаю – alaster 13 July 2018 в 13:03
  • 3
    Решение с синхронизированным блоком может быть приемлемым, но это не так. Проверка и добавление не являются атомарными. – grape_mao 13 July 2018 в 13:09
  • 4
    @grape_mao Я думал, что такой минимальной вероятности будет достаточно. Это хорошо сейчас? – alaster 13 July 2018 в 13:11

Вы можете использовать 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));
0
ответ дан Mạnh Quyết Nguyễn 17 August 2018 в 12:51
поделиться
  • 1
    COMPANY_ENTRANT.putIfAbsent вернет null, если ранее не было значения на карте. COMPANY_ENTRANT.computeIfAbsent(user.getCompany(), c -> new Semaphore(1)) должен работать – alaster 13 July 2018 в 13:14
  • 2
    в вашем случае два пользователя из одной компании будут работать один за другим – alaster 13 July 2018 в 13:15