code: @Override
    public void aggregate(final ByteBuffer buf, final int position)
    {
      for (int i = 0; i < valueSelectors.length; i++) {
        values[i] = valueSelectors[i].getDouble();
      }
      final IndexedInts keys = keySelector.getRow();
      // Wrapping memory and ArrayOfDoublesSketch is inexpensive compared to sketch operations.
      // Maintaining a cache of wrapped objects per buffer position like in Theta sketch aggregator
      // might might be considered, but it would increase complexity including relocate() support.
      final WritableMemory mem = WritableMemory.wrap(buf, ByteOrder.LITTLE_ENDIAN);
      final WritableMemory region = mem.writableRegion(position, maxIntermediateSize);
      final Lock lock = stripedLock.getAt(lockIndex(position)).writeLock();
      lock.lock();
      try {
        final ArrayOfDoublesUpdatableSketch sketch = ArrayOfDoublesSketches.wrapUpdatableSketch(region);
        for (int i = 0, keysSize = keys.size(); i < keysSize; i++) {
          final String key = keySelector.lookupName(keys.get(i));
          sketch.update(key, values);
        }
      }
      finally {
        lock.unlock();
      }
    }
docs: This method uses locks because it can be used during indexing,
  and Druid can call aggregate() and get() concurrently
  https://github.com/apache/incubator-druid/pull/3956