需要帮助设计“无限”线程

时间:2009-02-22 21:07:55

标签: java concurrency

我有一些数据库表,只要app正在运行,就需要一次处理5个记录。所以,它看起来像这样:

  1. 获取尚未处理的记录或其他线程现在不处理的记录。
  2. 处理它(这是一个依赖于互联网连接的漫长过程,因此它可能会超时/抛出错误。)
  3. 转到下一条记录。当表格从表面开始时开始。
  4. 我对线程没有多少经验,所以我看到两种可能的策略:

    接近A。

    1.创建新的ExecutorService:

    ExecutorService taskExecutor = Executors.newFixedThreadPool(5);
    

    2.向其添加5个任务:

    for (int i = 0; i < 5; i++) {
        taskExecutor.execute(new MyTask());
    }
    

    3.每个任务都是无限循环,即:从表中读取记录,处理它,然后获取另一个记录。

    这种方法的问题是如何通知其他线程当前正在处理哪些记录。为此,我可以使用表中的“status”字段,或者只使用一些包含当前处理ID的CopyOnWriteArraySet。

    接近B。

    1.创建相同的ExecutorService:

    ExecutorService taskExecutor = Executors.newFixedThreadPool(5);
    

    2。有一个无限循环,选择需要处理的记录并将它们传递给执行程序:

    while (true) {
        //get next record here
        taskExecutor.execute(new MyTask(record));
        //monitor the queue and wait until some thread is done processing,
        //so I can add another record
    }
    

    3.每个任务处理一条记录。

    这种方法的问题在于我需要将执行程序的队列中的任务添加到比处理它们更慢的速度,以免它们随着时间的推移而堆积起来。这意味着我不仅需要监控当前正在运行的任务,还需要监控它们何时完成处理,因此我可以将新记录添加到队列中。

    我个人认为第一种方法更好(更容易),但我觉得第二种方法更正确。你怎么看?或者也许我应该做一些完全不同的事情?

    如果需要,我也可以使用Spring或Quartz库。

    感谢。

5 个答案:

答案 0 :(得分:5)

我认为CompletionService(和ExecutorCompletionService)可以为您提供帮助。

您通过完成服务提交所有任务,并允许您等到一个线程(任何线程)完成其任务。这样,只要有空闲线程,您就可以提交下一个任务。这意味着您使用方法B.

伪代码:

Create ThreadPoolExecutor and ExecutorCompletionService wrapping it

while (true) {
  int freeThreads = executor.getMaximumPoolSize() - executor.getActiveCount()
  fetch 'freeThreads' tasks and submit to completion service (which
                                      in turn sends it to executor)

  wait until completion service reports finished task (with timeout)
}

等待时间超时可以帮助您避免队列中没有任务时的情况,因此所有线程都处于空闲状态,您将等待其中一个线程完成 - 这种情况永远不会发生。

您可以通过ThreadPoolExecutor方法检查可用线程数:getActiveCount(活动线程)和getMaximumPoolSize(最大可用配置线程数)。您需要直接创建ThreadPoolExecutor,或者转换从Executors.newFixedThreadPool()返回的对象,尽管我更喜欢直接创建...有关详细信息,请参阅Executors.newFixedThreadPool()方法的来源。

答案 1 :(得分:4)

另一种方法是使用大小为5的ArrayBlockingQueue。单个生产者线程将遍历表,最初填充它并在消费者处理它们时放入记录。五个消费者线程将各自获取()一条记录,处理它并返回另一条记录。这样生成器线程确保一次不向两个线程提供记录,并且消费者线程处理独立记录。 Java Concurrency in Practice可能会为您提供更多选择,对于此类问题是一个很好的解读。

答案 2 :(得分:1)

我会采用这种方法:

使用一个线程分发工作。这个线程将产生5个其他线程并进入休眠状态。当一个工作线程结束时,它将唤醒工作分配器线程,然后它会产生新的工作线程并进入睡眠状态......

答案 3 :(得分:1)

我会在MyTask中有一个静态集合

public class MyTask implements Runnable {
  private static ArrayList<RecordID> processed = new ArrayList<RecordID>();
  private static ArrayList<RecordID> processing = new ArrayList<RecordID>();

  private RecordID working = null;

  public void run() {
    for(;;) {
      synchronized( MyTask.class ) {
        Record r = getUnprocessedRecord(); // use processed and processing to do query
        if ( r == null ) {  // no more in table to process
          if ( processing.length == 0 ) { // nothing is processing
            processed.clear();  // this should allow us to get some results on the next loop
          }
          Thread.sleep( SLEEP_INTERVAL );
          continue;
        } else {
          working = r.getRecordID();
          processing.add( working );
        }
      }
      try {
        //do work
        synchronized( MyTask.class ) {
          processed.add(working);
        }
      } catch( Whatever w ){
      } finally {
        synchronized( MyTask.class ) {
          processing.remove(working);
        }
      } 
    }
  }

}

答案 4 :(得分:0)

我的观点,春天去QUARTZ吧。它是完美的选择。现在已经将它用于生产2年以上了。当有些人已经做到最好时,为什么要尝试重新发明轮子。更不用说它提供的不同运行模式。我建议至少尝试一下。