如何处理 Java“作业”同步?

发布于 2024-08-12 07:16:30 字数 933 浏览 2 评论 0 原文

我们有一组行动或“工作”,我们希望一次发生一个(而不是同时发生)。即:作业 A 不能在 B 发生时发生,并且不能同时运行两个 C 作业。

如果线程尝试同时运行作业,它们应该会收到错误。我们不应该只是对请求进行排队。

有些作业在用户发出请求时异步发生,我们返回状态消息和 ID,然后在服务器上异步处理作业。

我们正在寻求有关如何处理这种情况的建议。

一种选择是锁定共享对象

public class Global {
  public static final Object lock = new Object();
}

public class JobA {
    public void go() {
        synchronized(Global.lock) {
            //Do A stuff
        }
    }
}

public class JobB {
    public void go() {
        synchronized(Global.lock) {
            //Do B stuff
        }
    }
}

这样做的问题:

  1. 我们会将并发请求排队并且不返回错误消息(这是我们想要做的)
  2. 如果JobA想要将消息写入队列以异步处理,我们如何保证当 JobA 中的另一个方法从队列中读取消息时,该方法将能够在 JobA< 的另一个实例之前获取 Global.lock 锁/代码> 开始?

关于更好的方法有什么建议吗?

We have a set of actions or "Jobs" which we'd like to happen one at a time (not concurrently). Ie: Job A can't happen while B is happening, and you can't have two C jobs running at the same time.

In the event that a thread attempts to run a job concurrently they should get an error. We shouldn't just queue up the request.

Some jobs happen asynchronously where the user makes request, we return a status message and id, and then process job asynchronously on server.

We're looking for advice on how to handle this scenario.

One option is to lock on a shared object:

public class Global {
  public static final Object lock = new Object();
}

public class JobA {
    public void go() {
        synchronized(Global.lock) {
            //Do A stuff
        }
    }
}

public class JobB {
    public void go() {
        synchronized(Global.lock) {
            //Do B stuff
        }
    }
}

Problems with this:

  1. We would queue up concurrent requests and not return an error message (which we want to do)
  2. If JobA wants to write message to a queue to process asynchronously, how can we be assured that when the message is read from the queue by another method in JobA that the method will be able to acquire the Global.lock lock before another instance of JobA starts?

Any suggestions on a better approach?

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

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

发布评论

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

评论(4

漫雪独思 2024-08-19 07:16:30

您是否考虑过使用 单线程执行器 ?顾名思义,它使用单个线程来执行 Callables(类似于 Runnables - 请参阅文档),从而满足您对同步处理的要求。使用可用的 java.util.concurrent 类将极大地简化您的线程代码。

要处理在另一个作业正在运行时提交作业并生成错误的情况,请设置有界队列和拒绝处理程序。请参阅此页面和第 8.3.2/8.3.3 节特别是有关 ThreadPoolExecutor.setRejectedExecutionHandler()

Have you considered using a SingleThreadExecutor ? As its name suggests, it uses a single thread to execute Callables (similar to Runnables - see the doc) and thus satisfies your requirement for synchronous processing. Using the java.util.concurrent classes available will simplify your threading code enormously.

To handle the scenario when submitting a job when another job is running, and generating an error, set a bounded queue and a rejection handler. See this page and section 8.3.2/8.3.3 specifically for details on ThreadPoolExecutor.setRejectedExecutionHandler().

梦里兽 2024-08-19 07:16:30

听起来您需要的是一个独占锁,您可以在尝试获取锁时避免阻塞。

幸运的是,Lock 接口有一个 tryLock 方法,因此您可以执行以下操作。

final Lock lock = new ReentrantLock();

....

void attemptJob( Runnable runnable )
{
    if (!lock.tryLock())
        throw new ConcurrentJobException();

    try
    {
       runnable.run();
    }
    finally
    {
       lock.unlock();
    }
}

如果你想运行异步,那么你应该将 Runnable(或者 Callable,如果你愿意)委托给 ExecutorService,并在它完成时释放锁。

编辑:例如(加上一些最终声明)

final Lock lock = new ReentrantLock();
final ExecutorService service = Executors.newSingleThreadExecutor();

....

void attemptJob( final Runnable runnable )
{
    if (!lock.tryLock())
        throw new ConcurrentJobException();

    service.execute( new Runnable()
    {
        public void run()
        {
            try
            {
                runnable.run();
            }
            finally
            {
                lock.unlock();
            } 
        }
    });
}

完成后不要忘记 shutdown() ExecutorService。

Sounds like what you need is an exclusive lock where you can avoid blocking when trying to acquire the lock.

Fortunately the Lock interface has a tryLock method so you can do the following.

final Lock lock = new ReentrantLock();

....

void attemptJob( Runnable runnable )
{
    if (!lock.tryLock())
        throw new ConcurrentJobException();

    try
    {
       runnable.run();
    }
    finally
    {
       lock.unlock();
    }
}

If you want to run async then you should delegate the Runnable (or Callable if you prefer) to an ExecutorService and, when it completes, release the lock.

EDIT: e.g. (plus some more final declarations)

final Lock lock = new ReentrantLock();
final ExecutorService service = Executors.newSingleThreadExecutor();

....

void attemptJob( final Runnable runnable )
{
    if (!lock.tryLock())
        throw new ConcurrentJobException();

    service.execute( new Runnable()
    {
        public void run()
        {
            try
            {
                runnable.run();
            }
            finally
            {
                lock.unlock();
            } 
        }
    });
}

Don't forget to shutdown() the ExecutorService when you are done with it.

杀手六號 2024-08-19 07:16:30

看看 Spring Batch。我认为它可以帮助你。

Have a look at Spring Batch. I think it can help you.

夏花。依旧 2024-08-19 07:16:30

另一种可能性是使用已配置的 ThreadPoolExecutor。我相信这会起作用,但强烈鼓励读者亲自测试。

Executor executor = new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());

上面的内容大约是 newSingleThreadExecutor 在 Executors 类中所做的事情。

为了满足“并发任务错误”的要求,我认为您可以将队列实现更改为 SynchronousQueue。

Executor executor = new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new SynchronousQueue<Runnable>());

这应该允许一个任务运行,如果在第一个任务完成之前提交第二个任务,则将调用 ThreadPoolExecutor RejectedExecutionHandler,默认情况下会抛出 RejectedExecutionException。

Another possibility is to use a configured ThreadPoolExecutor. I believe this will work but strongly encourage readers to test it for themselves.

Executor executor = new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());

The above is approx what newSingleThreadExecutor does in the Executors class.

To satisfy the "error on concurrent task" requirement I think you can just change the queue implementation to a SynchronousQueue.

Executor executor = new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new SynchronousQueue<Runnable>());

This should allow one task to run and if a second if submitted before the first finishes the ThreadPoolExecutor RejectedExecutionHandler will get called, which by default throws a RejectedExecutionException.

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