java: 执行者 +任务 +锁

发布于 2024-10-15 17:06:18 字数 2040 浏览 5 评论 0原文

假设我有一个 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

其中 SomeObjReentrantLock (获取 = tryLock() 并释放 = unlock()) 或 AtomicBoolean 或其他东西,但是我不确定是哪一个。我这里需要一个ReentrantLock吗? (也许我想要一个不可重入的锁,以防 algorithmX() 导致此任务递归运行!)或者 AtomicBoolean 就足够了吗?


编辑:对于不可重入锁,这合适吗?

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.
        */
   }
}

Suppose I have an ExecutorService (which can be a thread pool, so there's concurrency involved) which executes a task at various times, either periodically or in response to some other condition. The task to be executed is the following:

  • if this task is already in progress, do nothing (and let the previously-running task finish).
  • if this task is not already in progress, run Algorithm X, which can take a long time.

I'm trying to think of a way to implement this. It should be something like:

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

where SomeObj is either a ReentrantLock (acquire = tryLock() and release = unlock()) or an AtomicBoolean or something, but I'm not sure which. Do I need a ReentrantLock here? (Maybe I want a non-reentrant lock in case algorithmX() causes this task to be run recursively!) Or would an AtomicBoolean be enough?


edit: for a non-reentrant lock, is this appropriate?

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.
        */
   }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

瀞厅☆埖开 2024-10-22 17:06:18

您建议的锁实现很弱,因为有人很容易不当使用它。

下面是一个更有效的实现,与您的实现具有相同的不当使用弱点:

   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);
   }

The lock implementation you suggest is weak in the sense that it would be quite easy for someone to use it improperly.

Below is a much more efficient implementation with the same improper use weaknesses as your implementation:

   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);
   }
热情消退 2024-10-22 17:06:18

您的第一段代码看起来相当不错,但如果您担心 algorithmX 递归调用该任务,我建议您使用 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();
          }
       }
   }
}

特别注意尝试获取的使用。如果获取锁失败,则不会运行algorithmX

Your first bit of code looks pretty good, but if you're worried about algorithmX recursively invoking the task, I would suggest you use a java.util.concurrent.Semaphore as the synchronization object, rather than a ReentrantLock. For example:

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

Note in particular the use of tryacquire. If acquiring the lock fails, algorithmX is not run.

韵柒 2024-10-22 17:06:18

ReentrantLock 对我来说似乎很好。我觉得使用 AtomicInteger 手动创建锁有趣的唯一情况是,如果您的 algorithmX 非常短,而这不是您的情况。

ReentrantLock seems fine to me. The only situation where I'd find interesting to manually create a lock using AtomicInteger will be if you have a really short algorithmX which is not your case.

穿越时光隧道 2024-10-22 17:06:18

我认为选择正确的锁实现的秘诀是:
* 如果此任务已在进行中,则不执行任何操作(并让先前运行的任务完成)。

在这种情况下“什么也不做”是什么意思?运行algorithmX完成后,线程应该阻塞并重试执行吗?。如果是这种情况,应使用 semaphore.acquire 而不是 tryAcquire,并且 AtomicBoolean 解决方案将无法按预期工作。

I think the secret of choosing the right lock impl is this:
* if this task is already in progress, do nothing (and let the previously-running task finish).

What does "do nothing" mean in this context? Thread should block and retry execution after running algorithmX is finished?. If this is the case semaphore.acquire instead of tryAcquire should be used and AtomicBoolean solution won't work as expected.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文