用于执行并发任务的模式只需一次

时间:2011-06-30 07:29:23

标签: java design-patterns concurrency

我正在研究一个java服务器,它调度xmpp消息,工作人员从我的客户端执行任务。

private static ExecutorService threadpool = Executors.newCachedThreadPool();

DispatchWorker worker = new DispatchWorker(connection, packet);
threadpool.execute(worker);

工作正常,但我还需要更多。

  1. 我不想多次执行相同的请求。
  2. 我的工作人员可以启动另一个带有后台任务的线程,也只允许一次运行一次。工作线程中的Threadpool。
  3. 我可以通过字符串识别请求,我也可以给backround任务一个id来识别它们。

    我的解决方案是一个同步的hashmap,我的正在运行的任务是用他们的id注册的。地图的引用将传递给工作线程,它们在完成后删除它们的条目。

    感觉这个解决方案有点笨拙,所以我想知道是否有更优雅的模式/最佳实践。

    最好的问候,m

5 个答案:

答案 0 :(得分:0)

您可以使用Singleton线程池或将线程池作为参数传递。 (我会有游泳池final

您可以使用HashSet来防止添加重复的任务。

答案 1 :(得分:0)

我相信使用Map是可以的。但是,您可以使用HashMap代替同步ConcurrenHashMap,它允许您指定并发级别,即同时有多少线程可以使用map。而且它还具有原子putIfAbsent操作。

答案 2 :(得分:0)

这正是Quartz所做的事情(虽然它做得更多,比如将来安排工作)。

答案 3 :(得分:0)

我会使用始终在运行的队列和守护进程工作线程并等待某些东西进入队列。这样可以保证只有一个工作人员正在处理请求。 如果您只想运行一个线程,请将POOLSIZE向下转为1,或使用newSingleThreadExecutor。

我不太明白你的第二个要求:你的意思是只允许一个线程作为后台任务运行吗?如果是这样,您可以创建另一个SingleThreadExecutor并将其用于后台任务。那么POOLSIZE> 1就没有多大意义,除非在后台线程中完成的工作与工作者本身相比非常短。

private static interface Request {};
private final int POOLSIZE = 10;
private final int QUEUESIZE = 1000;
BlockingQueue<Request> e = new LinkedBlockingQueue<Request>(QUEUESIZE);

public void startWorkers() {
    ExecutorService threadPool = Executors.newFixedThreadPool(POOLSIZE);
    for(int i=0; i<POOLSIZE; i++) {
        threadPool.execute(new Runnable() {

            @Override
            public void run() {
                try {
                    final Request request = e.take();
                    doStuffWithRequest(request);
                } catch (InterruptedException e) {
                    // LOG
                    // Shutdown worker thread.
                }
            }
        });
    }
}
public void handleRequest(Request request) {
    if(!e.offer(request)) {
        //Cancel request, queue is full;
    }
}

在启动时,初学者开始工作(惊喜!)。 handleRequest处理来自webservice,servlet或其他任何内容的请求。

当然,您需要根据需要调整“Request”和“doStuffWithRequest”,并为关闭等添加一些额外的逻辑。

答案 4 :(得分:0)

我们原来wrote our own utilities来处理这个问题,但是如果你想要记住结果,那么GuavaComputingMap将初始化封装为一个且只有一个线程(其他线程阻塞和等待结果)和备忘录。

它还支持各种过期策略。

用法很简单,您可以使用初始化函数构建它:

Map<Long, Foo> cache = new MapMaker().makeComputingMap(new Function<Long, Foo>() {
  public Foo apply(String key) {
    return … // init with expensive calculation
  }
});

然后只需称呼它:

Foo foo = cache.get("key");

要求“密钥”的第一个主题是执行初始化的人