我正在尝试以计划的非阻塞方式进行一些阻塞操作(例如HTTP请求)。假设我有10个请求,一个请求耗时3秒,但我不想等待3秒,而是等待1秒发送下一个请求。完成所有执行后,我希望将所有结果收集到列表中并返回给用户。
下面是我的场景的原型(线程睡眠用作阻塞操作,而不是HTTP请求)。
public static List<Integer> getResults(List<Integer> inputs) throws InterruptedException, ExecutionException {
List<Integer> results = new LinkedList<Integer>();
Queue<Callable<Integer>> tasks = new LinkedList<Callable<Integer>>();
List<Future<Integer>> futures = new LinkedList<Future<Integer>>();
for (Integer input : inputs) {
Callable<Integer> task = new Callable<Integer>() {
public Integer call() throws InterruptedException {
Thread.sleep(3000);
return input + 1000;
}
};
tasks.add(task);
}
ExecutorService es = Executors.newCachedThreadPool();
ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);
ses.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Callable<Integer> task = tasks.poll();
if (task == null) {
ses.shutdown();
es.shutdown();
return;
}
futures.add(es.submit(task));
}
}, 0, 1000, TimeUnit.MILLISECONDS);
while(true) {
if(futures.size() == inputs.size()) {
for (Future<Integer> future : futures) {
Integer result = future.get();
results.add(result);
}
return results;
}
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
List<Integer> results = getResults(new LinkedList<Integer>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)));
System.out.println(Arrays.toString(results.toArray()));
}
我正在等待while循环,直到所有任务都返回正确的结果。但是它永远不会进入破坏条件,并且会无限循环。每当我进行诸如logger或断点之类的I / O操作时,它都会中断while循环,一切都会好起来。
我对Java并发还比较陌生,试图了解正在发生的事情以及这是否是正确的方法。我想I / O操作会在线程调度程序上触发某些事件,并使其检查集合的大小。
答案 0 :(得分:1)
您需要同步线程。您有两个不同的线程(主线程和exectuor服务线程)访问futures
列表,并且由于LinkedList
不同步,因此这两个线程看到futures
的两个不同值。>
while(true) {
synchronized(futures) {
if(futures.size() == inputs.size()) {
...
}
}
}
发生这种情况是因为Java中的线程使用cpu缓存来提高性能。因此,每个线程在同步之前可能具有不同的变量值。 该SO question对此有更多信息。
也来自this答案:
所有与内存有关。线程通过共享内存进行通信,但是当系统中有多个CPU时,所有CPU都试图访问同一个内存系统,那么内存系统将成为瓶颈。因此,允许典型的多CPU计算机中的CPU进行延迟,重新排序和缓存操作,以加快处理速度。
这在线程之间不进行交互时很有效,但是当它们实际上要进行交互时会引起问题:如果线程A将值存储到一个普通变量中,则Java无法保证线程何时(或即使) B会看到值的变化。
为了克服重要的问题,Java为您提供了一些同步线程的方法。也就是说,使线程在程序内存的状态上达成共识。 volatile关键字和synced关键字是在线程之间建立同步的两种方法。
最后,futures
列表不会在您的代码中更新,因为由于无限while
块的存在,主线程一直被占用。在while循环中执行任何I / O操作都会为cpu提供足够的呼吸空间来更新其本地缓存。
无限的while循环通常是一个坏主意,因为它非常占用资源。在下一次迭代之前添加一个小的延迟可以使它更好一些(尽管效率仍然很低)。