【问题标题】:Ideas on concurrent datastructure并发数据结构思路
【发布时间】:2014-08-12 02:38:26
【问题描述】:

我不确定我能否以最清晰的方式提出我的问题,但我会尽力而为。

假设我正在从第三方 api 检索一些信息。检索到的信息将非常庞大。为了提高性能,我不是一次性检索所有信息,而是以分页方式检索信息(api 为我提供了该工具,基本上是一个迭代器)。返回类型基本上是一个对象列表。

我的目标是处理我手头的信息(包括比较和存储在数据库中以及许多其他操作),同时我得到请求的分页响应。

我在这里向专家社区提出的问题是,在这种情况下您更喜欢哪种数据结构。在这种情况下,像 Spring Batch 这样的框架是否可以帮助您获得性能提升。

我知道这个问题有点含糊,但我正在寻找一般的想法、提示和指示。

【问题讨论】:

  • 问题确实有点含糊。在推断数据结构偏好时,您是指从 3rd 方 API 返回的数据结构吗?通常我们在这些问题上没有选择 :( 框架通常不用于提高性能。通常,开发人员将它们用于可扩展性(意味着我们必须做更少的样板代码,或者重写我们已经存在的功能重复使用)
  • 返回类型基本上是对象的ArrayList。
  • @thePoly_glot 请记住,调用 API 很有可能是调用中最昂贵的部分。将所有数据放在一个块中通常是最好的主意。您是否完成了其他建议的分析?
  • @Patrick Collins 是的。这是我找到瓶颈所在的第一件事。是的,从 api 获取信息是最昂贵的部分。所以我想通了为什么不在 api 向我发送数据时做一些有用的事情。
  • 我想我也必须投票“太模糊”。这里的并发性与您的处理有关,而不是 IO 很慢并且您以块的形式读取数据的事实。因此,我们需要了解您的所有任务,可能还需要了解所有数据,然后才能说出有用的信息。

标签: java multithreading concurrency spring-batch


【解决方案1】:

在这些情况下,我的数据结构是 java.util.concurrent.CompletionService。

出于示例的目的,我将假设几个额外的约束:

  • 您一次只希望向远程服务器发送一个未完成的请求
  • 您希望按顺序处理结果。

这里是:

// a class that knows how to update the DB given a page of results
class DatabaseUpdater implements Callable { ... }
// a background thread to do the work
final CompletionService<Object> exec = new ExecutorCompletionService(
   Executors.newSingleThreadExecutor());

// first call
List<Object> results = ThirdPartyAPI.getPage( ... );
// Start loading those results to DB on background thread
exec.submit(new DatabaseUpdater(results));

while( you need to ) {
  // Another call to remote service
  List<Object> results = ThirdPartyAPI.getPage( ... );
  // wait for existing work to complete
  exec.take(); 
  // send more work to background thread
  exec.submit(new DatabaseUpdater(results));
}
// wait for the last task to complete
exec.take(); 

这只是一个简单的双线程设计。第一个线程负责从远程服务获取数据,第二个线程负责写入数据库。

DatabaseUpdater 抛出的任何异常都会在获取结果时传播到主线程(通过exec.take())。

祝你好运。

【讨论】:

    【解决方案2】:

    就实际的并行性而言,Java 中一个非常有用的构造是ThreadPoolExecutor。大概的样子是这样的:

    public class YourApp {
        class Processor implements Runnable {
            Widget toProcess;
    
            public Processor(Widget toProcess) {
                this.toProcess = toProcess;
            }
    
            public void run() {
                // commit the Widget to the DB, etc
            }
        }
    
        public static void main(String[] args) {
    
            ThreadPoolExecutor executor = 
                new ThreadPoolExecutor(1, 10, 30, 
                                       TimeUnit.SECONDS, 
                                       new LinkedBlockingDeque());
    
            while(thereAreStillWidgets()) {
                ArrayList<Widget> widgets = doExpensiveDatabaseCall();
                for(Widget widget : widgets) {
                    Processor procesor = new Processor(widget);
                    executor.execute(processor);
                }
            }
    
        }
    
    }
    

    但正如我在评论中所说:调用外部 API 昂贵。最好的策略很可能是在一次调用中从 API 中提取所有 Widget 对象,然后在获得它们后并行处理它们。执行更多的 API 调用会给您带来每次从服务器一直向您发送数据的开销——最好尽可能少地支付该成本。

    另外,请记住,如果您正在执行 DB 操作,您的 DB 可能不允许并行写入,因此您可能会因此而变慢。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-18
      • 1970-01-01
      • 2012-04-30
      • 1970-01-01
      • 2023-04-08
      • 1970-01-01
      相关资源
      最近更新 更多