code: public CompletableFuture<List<V>> loadMany(List<K> keys, List<Object> keyContexts) {
          nonNull(keys);
          nonNull(keyContexts);
  
          synchronized (this) {
              List<CompletableFuture<V>> collect = new ArrayList<>();
              for (int i = 0; i < keys.size(); i++) {
                  K key = keys.get(i);
                  Object keyContext = null;
                  if (i < keyContexts.size()) {
                      keyContext = keyContexts.get(i);
                  }
                  collect.add(load(key, keyContext));
              }
              return CompletableFutureKit.allOf(collect);
          }
      }
docs: Requests to load the list of data provided by the specified keys asynchronously, and returns a composite future
  of the resulting values.
  <p>
  If batching is enabled (the default), you'll have to call {@link DataLoader#dispatch()} at a later stage to
  start batch execution. If you forget this call the future will never be completed (unless already completed,
  and returned from cache).
  <p>
  The key context object may be useful in the batch loader interfaces such as {@link org.dataloader.BatchLoaderWithContext} or
  {@link org.dataloader.MappedBatchLoaderWithContext} to help retrieve data.
  
  @param keys        the list of keys to load
  @param keyContexts the list of key calling context objects
  
  @return the composite future of the list of values