private void runConcurrencyTest(List<DiskSpaceRequirements> requirementsList, final boolean allocate,
      final boolean free) throws Exception {
    List<Callable<Void>> tasks = new ArrayList<>();
    final String filenamePrefix = "conc-test-";
    int fileCount = 0;
    for (final DiskSpaceRequirements requirements : requirementsList) {
      final AtomicReference<String> storeId = new AtomicReference<String>(requirements.getStoreId());
      for (int i = 0; i < requirements.getSegmentsNeeded(); i++, fileCount++) {
        final String filename = filenamePrefix + fileCount;
        tasks.add(() -> {
          if (allocate) {
            allocateAndVerify(storeId.get(), filename, requirements.getSegmentSizeInBytes(), false);
          }
          if (free) {
            freeAndVerify(storeId.get(), new File(allocatedFileDir, filename), requirements.getSegmentSizeInBytes(),
                false);
          }
          return null;
        });
      }
    }
    for (Future<Void> future : exec.invokeAll(tasks)) {
      future.get(10, TimeUnit.SECONDS);
    }
  }
Run a concurrency test with multiple parallel tasks, each one allocating and/or freeing a file called
"conc-test-{n}"
@param requirementsList The list of {@link DiskSpaceRequirements} used to determine the number of operations to
                        start
@param allocate {@code true} if each task should allocate a new segment.
@param free {@code true} if each task should free a (hopefully) existing segment.
ambry-store/src/test/java/com/github/ambry/store/DiskSpaceAllocatorTest.java