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

时间:2009-11-28 13:54:39

标签: java concurrency synchronization

我们有一套行动或“工作”,我们希望一次发生一次(不同时发生)。即:当B发生时,作业A不会发生,并且您不能同时运行两个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中的另一种方法从队列中读取消息时该方法将能够在Global.lock的另一个实例开始之前获取JobA锁定?
  3. 有关更好方法的任何建议吗?

4 个答案:

答案 0 :(得分:8)

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

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

答案 1 :(得分:7)

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

幸运的是,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。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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