在多个线程中调用相同的API

时间:2018-04-05 08:37:38

标签: java multithreading

我想从公开的API中获取数据并将数据插入到数据库中 API返回包含两个对象的JSON:Page对象和Activities数组对象 在Page对象中,可以看到有400个页面,所以我需要调用API 400次并将每个调用的Activity存储到DB中。
由于调用API 400次是非常耗时的,我想使用多线程来提高速度,但是,代码表现得很奇怪,传递的页码看起来不按顺序排列,在控制台中我几乎立即看到索引达到numberOfPages 400。

我试过了,我不确定会出现什么问题:

private static Map<Integer, ActivityResult> activities = new ConcurrentHashMap<Integer, ActivityResult>();

public static void fetchActivities(){
    ExecutorService es = Executors.newFixedThreadPool(20);

    String activityResponse = runRequest("/activity/", first_page);
    ActivityResult ar = (ActivityResult) gson.fromJson(activityResponse, ActivityResult.class);
    activities.put(first_page, ar);
    int numberOfPages = ar.getPaging().getPages();
    AtomicInteger index = new AtomicInteger(first_page +1);
    for(index.get(); index.get() < numberOfPages; index.incrementAndGet()){
    es.submit(new Runnable() {
            @Override
            public void run() {
                 System.out.println(index.get());
                 String tmpResponse = runRequest("/activity/", index.get());
                 activities.put(index.get(), gson.fromJson(tmpResponse, ActivityResult.class));          
            }
        });
    }
    es.shutdown();
    System.out.println(activities.size());
}

runRequest方法使用okhttp3调用API,它应该是线程安全的。

1 个答案:

答案 0 :(得分:2)

你的问题在这里:

AtomicInteger index = new AtomicInteger(first_page +1);
for(index.get(); index.get() < numberOfPages; index.incrementAndGet()){
    es.submit(new Runnable() {
        @Override
        public void run() {
             System.out.println(index.get());
             String tmpResponse = runRequest("/activity/", index.get());
             activities.put(index.get(), gson.fromJson(tmpResponse, ActivityResult.class));          
        }
    });
}

您没有使用AtomicInteger的原子性。 for循环在父线程上执行得非常快,并提交所有400个任务。 20将很快开始(但不一定立即),其他380将排队等候。 index已经一直增加到400.任务已经提交但它们可能无法启动,直到将来某个不确定的点。

然后启动任务并使用index最新值。队列中的任何任务(在您的情况下为任务20-400)都可能都在for循环完成后开始,index.get将为所有这些任务返回400。前20个任务可能会从for-loop中间开始,并会获得一组随机值。

正确的代码看起来应该是这样的(我还将Runnable转换为lambda):

AtomicInteger index = new AtomicInteger(first_page + 1);
for(int i = first_page; i < numberOfPages; ++i){
    es.submit(() -> {
       int page = index.incrementAndGet();
       System.out.println(page);
       String tmpResponse = runRequest("/activity/", page);
       activities.put(page , gson.fromJson(tmpResponse, ActivityResult.class));          
   });
}