/** * Atomically gets an existing MetricProxy from the given cache or creates a new one and adds it. * * @param cache The Cache to get or insert into. * @param name Metric/Proxy name. * @param createMetric A Function that creates a new Metric given its name. * @param createProxy A Function that creates a MetricProxy given its input. * @param <T> Type of Metric. * @param <V> Type of MetricProxy. * @return Either the existing MetricProxy (if it is already registered) or the newly created one. */ private <T extends Metric, V extends MetricProxy<T>> V getOrSet(ConcurrentHashMap<String, V> cache, String name, Function<String, T> createMetric, ProxyCreator<T, V> createProxy) { // We could simply use Map.computeIfAbsent to do everything atomically, however in ConcurrentHashMap, the function // is evaluated while holding the lock. As per the method's guidelines, the computation should be quick and not // do any IO or acquire other locks, however we have no control over new Metric creation. As such, we use optimistic // concurrency, where we assume that the MetricProxy does not exist, create it, and then if it does exist, close // the newly created one. T newMetric = createMetric.apply(name); V newProxy = createProxy.apply(newMetric, name, cache::remove); V existingProxy = cache.putIfAbsent(newProxy.getProxyName(), newProxy); if (existingProxy != null) { newProxy.close(); newMetric.close(); return existingProxy; } else { return newProxy; } }