执行程序任务,用于并行化方法

时间:2018-08-23 01:43:36

标签: java multithreading future executorservice callable

因此,我试图从磁盘中删除listDir中列出的n个文件,从而将listDir分为4部分,并使其从磁盘中并行删除。这里的目的是并行执行此操作,以使其快速而不是顺序进行。 deleteObject(x,credential,token)可以假定为一个API,该API最终从磁盘上删除了一个对象,并且是一个原子操作。成功删除后返回true,否则返回false

我在这里有几个问题

  1. 所以我有4个并行方法,虽然我通过invokeAll执行 而且Executors.newFixedThreadPool(4)声明了4个线程 总是会有1个线程分配给1个方法吗?
  2. 我是否需要在parallelDeleteOperation()方法的for循环中对迭代器“ i”进行同步并使用volatile。我问这个问题的原因是假设第一个线程是否尚未完成其任务(删除listDir1和for循环尚未完成),并在中途进行了上下文切换,第二个线程开始执行相同的任务(删除listDir1)。只是想知道在这种情况下第二个线程是否可以获取IndexOutOfBound异常。
  3. 将list分成4个部分并执行它,而不是让多个线程在一个很大的list上执行delete操作,有什么好处。
  4. 如果ExecutorService的操作之一返回false,则整个deleteMain()API都将返回false

     private boolean deleteMain(String parent, List<Structure>
        listDir, String place, String node, Sequence<String>
                                   groups, String Uid) throws IOException {
    
    int noCores = Runtime.getRuntime().availableProcessors();
    List<List<Integer>> splittedList = splitList(listDir, noCores);
    
    System.out.println(splittedList.size());
    
    System.out.println("NoOfCores" + noCores);
    Set<Callable<Boolean>> callables = new HashSet<Callable<Boolean>>();
    for (int i = 0; i < splittedList.size(); i++) {
        List<Integer> l = splittedList.get(i);
        callables.add(new Callable<Boolean>() {
            @Override
            public Boolean call() throws Exception {
    
                return parallelDeleteOperation(parent, listDir, place, node, groups Uid);
            }
        });
    }
    
    ExecutorService service = Executors.newFixedThreadPool(noCores);
    
    try {
        List<Future<Boolean>> futures = service.invokeAll(callables);
    
    
        for (Future<Boolean> future : futures) {
            if (future.get() != true)
                return future.get();
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
    service.shutdown();
    return true;
    }
    
    private Boolean parallelDeleteOperation(String parent, List<Structure>
        listDir, String place, String node, Sequence<String>
                                                groups, String Uid) throws IOException {
    for (int i = 0; i < listDir.size(); i++) {
        final String name = listDir.get(i).filename;
    
        final String filePath = "/" + (parent.isEmpty() ? "" : (parent +
                "/")) + name;
        final DeleteMessage message = new DeleteMessage(name, place, node
                filePath);
        final boolean Status = delete(message, groups, Uid, place);
        if (Status != true)
            return Status;
    }
    return true;
    }
    

2 个答案:

答案 0 :(得分:1)

  
      
  1. 所以我有4个并行方法,虽然我通过invokeAll来执行,并且Executors声明了4个线程。newFixedThreadPool(4)会总是有1个线程分配给1个方法?
  2.   

通常,如果任务数量等于池中的线程是正确的,但不能保证。这在很大程度上取决于池中的任务。

        Executors.newFixedThreadPool(4).invokeAll(IntStream.range(0, 8).mapToObj(i -> (Callable<Integer>) () -> {
            System.out.println(Thread.currentThread().getName() + ": " + i);
            return 0;
        }).collect(Collectors.toList()));

如果池中有更多任务(如上所述),则输出如下。没有明显的可预测规则来处理任务:

pool-1-thread-1: 0
pool-1-thread-2: 1
pool-1-thread-3: 2
pool-1-thread-1: 4
pool-1-thread-1: 5
pool-1-thread-1: 6
pool-1-thread-1: 7
pool-1-thread-4: 3
  
      
  1. 我是否需要在parallelDeleteOperation()方法的for循环中对迭代器“ i”进行同步并使用volatile。
  2.   

不,您不需要。您已经将原始列表拆分为单独的四个列表

在您的代码中:

  

最终列表listDir1 = listDir.subList(0,listDir.size()/ 4);

关于您的第3个问题:

  
      
  1. 将list分成4个部分并执行它,而不是让多个线程在一个很大的list上执行delete操作,有什么好处。
  2.   

您最好在实际条件下进行一些测试。太复杂了,不能说是好还是不好。

当您删除大列表上的 时,竞争条件可能比预先拆分列表更为严重,这可能会增加额外的开销。

此外,即使没有任何并行性,其性能也可能不错。当涉及到多用户系统时,由于线程上下文切换的开销,并行化版本甚至可能比顺序版本差。

您必须对其进行测试,并且测试助手代码可以为:

    Long start = 0L;
    List<Long> list = new ArrayList<>();
    for (int i = 0; i < 1_000; ++i) {
        start = System.nanoTime();
        // your method to be tested;
        list.add(System.nanoTime() - start);
    }
    System.out.println("Time cost summary: " + list.stream().collect(Collectors.summarizingLong(Long::valueOf)));
  
      
  1. 如果ExecutorService的操作之一返回false,则整个deleteMain()API都将返回false
  2.   

我想重构您的代码,以使其更简洁并满足您的最后要求(第4条):

// using the core as the count;
// since your task is CPU-bound, we can directly use parallelStream;
private static void testThreadPool(List<Integer> listDir) {
    // if one of the tasks failed, you got isFailed == true;
    boolean isFailed = splitList(listDir, Runtime.getRuntime().availableProcessors()).stream()
            .parallel().map(YourClass::parallelDeleteOperation).anyMatch(ret -> ret == false); // if any is false, it gives you false
}


// split up the list into "count" lists;
private static <T> List<List<T>> splitList(List<T> list, int count) {
    List<List<T>> listList = new ArrayList<>();
    for (int i = 0, blockSize = list.size() / count; i < count; ++i) {
        listList.add(list.subList(i * blockSize, Math.min((i+1) * blockSize, list.size()));
    }
    return listList;
}

答案 1 :(得分:0)

我看不到将列表分为4个子列表的任何优势:线程终止其列表后,它将立即处于空闲状态。如果您为输入列表中的每个元素提交任务,则所有四个线程将处于活动状态,直到队列为空。

更新: 就像有人指出的那样,您可以使用parallelStream,它更简单并且可能更快;但是如果您想保留ExecutorService,则可以执行以下操作:

    int noCores = Runtime.getRuntime().availableProcessors();
    List<Future<Boolean>> futures = new ArrayList<>();
    ExecutorService service = Executors.newFixedThreadPool(noCores);
    try {
        for (Structure s: listDir) {
            String name = s.filename;
            String filePath = "/" + (parent.isEmpty() ? "" : (parent
                + "/")) + name;
            Future<Boolean> result = service.submit(()-> {
                final DeleteMessage message = new DeleteMessage(
                        name, place, node, filePath);
                return delete(message, groups, Uid, place);
            });
            futures.add(result);
        }
    } finally {
        service.shutdown();
    }