【问题标题】:How to handle Java "jobs" synchronously?如何同步处理Java“工作”?
【发布时间】:2009-11-28 13:54:38
【问题描述】:

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

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

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

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

一个选项是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
        }
    }
}

这个问题:

  1. 我们会将并发请求排队并且不返回错误消息(我们想要这样做)
  2. 如果JobA想将消息写入队列进行异步处理,如何保证当JobA中的另一个方法从队列中读取消息时,该方法能够获取到@987654325 @lock 在另一个JobA 实例启动之前?

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

【问题讨论】:

  • 如果你解释一下 A. B. 和 C 的实际作用,可能会更容易理解。

标签: java concurrency synchronization


【解决方案1】:

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

要处理在另一个作业正在运行时提交作业并生成错误的场景,请设置有界队列和拒绝处理程序。有关ThreadPoolExecutor.setRejectedExecutionHandler() 的详细信息,请参阅this page 和第 8.3.2/8.3.3 节。

【讨论】:

  • 使用 SingleThreadExecutor 不满足提交作业而另一个仍在运行时出错的要求。它只会在第一个作业完成时阻塞并执行。
  • 然后使用最大长度为 1 的有界队列并配置为抛出异常而不是阻塞。
  • 也许您可以使用 ThreadPoolExecutor,corePoolSize=1、maximumPoolSize=1 和 workQueue=new ArrayBlockingQueue(1)。
  • 具体设置拒绝策略为AbortPolicy(java.sun.com/javase/6/docs/api/java/util/concurrent/…)
【解决方案2】:

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

幸运的是 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();
            } 
        }
    });
}

完成后不要忘记关闭() ExecutorService。

【讨论】:

  • 谢谢。您能否使用ExecutorService 提供有关异步的更多详细信息?
【解决方案3】:

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

【讨论】:

    【解决方案4】:

    另一种可能性是使用已配置的 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。

    【讨论】:

      猜你喜欢
      • 2021-11-07
      • 2010-10-19
      • 2012-01-12
      • 2019-08-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-10-27
      相关资源
      最近更新 更多