Java: исполнители + задачи + блокировки - PullRequest
5 голосов
/ 02 февраля 2011

Предположим, у меня есть ExecutorService (который может быть пулом потоков, поэтому существует параллелизм), который выполняет задачу в разное время, либо периодически, либо в ответ на какое-то другое условие. Задача, которую нужно выполнить, следующая:

  • если эта задача уже выполняется, ничего не делать (и позволить завершиться ранее выполненной задаче).
  • если эта задача еще не выполняется, запустите алгоритм X, который может занять много времени.

Я пытаюсь придумать, как это реализовать. Это должно быть что-то вроде:

Runnable task = new Runnable() {
   final SomeObj inProgress = new SomeObj();
   @Override public void run() {
       if (inProgress.acquire())
       {
          try
          {
             algorithmX();
          }
          finally
          {
             inProgress.release();
          }
       }
   }
}

// re-use this task object whenever scheduling the task with the executor

где SomeObj - это либо ReentrantLock (приобретать = tryLock() и выпуск = unlock()), либо AtomicBoolean или что-то еще, но я не уверен, какой именно. Мне нужен ReentrantLock здесь? (Может быть, я хочу блокировку без повторного входа в случае, если algorithmX() вызывает рекурсивное выполнение этой задачи!) Или будет достаточно AtomicBoolean?


edit: для одноразовой блокировки это уместно?

Runnable task = new Runnable() {
   boolean inProgress = false;
   final private Object lock = new Object();
   /** try to acquire lock: set inProgress to true, 
    *  return whether it was previously false
    */ 
   private boolean acquire() {
      synchronized(this.lock)
      {
         boolean result = !this.inProgress;
         this.inProgress = true;
         return result;
      }
   }
   /** release lock */
   private void release() {
      synchronized(this.lock)
      {
         this.inProgress = false;
      }
   }
   @Override public void run() {
       if (acquire())
       {
          // nobody else is running! let's do algorithmX()
          try
          {
             algorithmX();
          }
          finally
          {
             release();
          }
       }
       /* otherwise, we are already in the process of 
        * running algorithmX(), in this thread or in another,
        * so don't do anything, just return control to the caller.
        */
   }
}

Ответы [ 4 ]

2 голосов
/ 02 февраля 2011

Реализация блокировки, которую вы предлагаете, является слабой в том смысле, что кому-то было бы довольно легко использовать ее ненадлежащим образом.

Ниже приведена гораздо более эффективная реализация с теми же недостатками неправильного использования, что и ваша реализация:

   AtomicBoolean inProgress = new AtomicBoolean(false)
   /* Returns true if we acquired the lock */
   private boolean acquire() {
       return inProgress.compareAndSet(false, true);
   }
   /** Always release lock without determining if we in fact hold it */
   private void release() {
       inProgress.set(false);
   }
2 голосов
/ 02 февраля 2011

Ваш первый фрагмент кода выглядит довольно хорошо, но если вы беспокоитесь о алгоритмеX , рекурсивно вызывающем задачу, я бы предложил вам использовать java.util.concurrent.Semaphore в качестве объекта синхронизации, а не ReentrantLock .Например:

Runnable task = new Runnable() {
   final Semaphore lock = new Semaphore( 1 );
   @Override public void run() {
       if (lock.tryAcquire())
       {
          try
          {
             algorithmX();
          }
          finally
          {
             lock.release();
          }
       }
   }
}

Обратите внимание, в частности, на использование try acqu .Если получить блокировку не удалось, gorithX не будет запущен.

0 голосов
/ 04 февраля 2011

Думаю, секрет правильного выбора блокировки заключается в следующем: * если эта задача уже выполняется, ничего не делать (и позволить завершиться ранее выполненной задаче).

Что означает "ничего не делать"значит в этом контексте?Поток должен заблокировать и повторить выполнение после выполнения attributeX ?В этом случае следует использовать semaphore.acquire вместо tryAcquire , и решение AtomicBoolean не будет работать должным образом.

0 голосов
/ 02 февраля 2011

ReentrantLock мне кажется нормальным. Единственная ситуация, в которой мне было бы интересно создать блокировку вручную с помощью AtomicInteger, будет, если у вас действительно короткий algorithmX, который не подходит для вас.

...