diff --git a/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java b/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java index a1c7055a8972c..4728e00d29446 100644 --- a/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java +++ b/pulsar-common/src/main/java/org/apache/pulsar/common/naming/TopicName.java @@ -71,12 +71,32 @@ public static TopicName get(String domain, String tenant, String namespace, Stri return TopicName.get(name); } + /** + * Get or create a TopicName from the cache. + * + *

Optimization over {@code computeIfAbsent}: avoids holding the ConcurrentHashMap bin-lock + * while constructing a new TopicName object. The construction (string splitting / parsing) is + * pure CPU work and can be done outside the lock. In the typical steady-state (cache hit) this + * method does a single volatile read via {@code get()} and returns immediately with no + * synchronization overhead. + * + *

In the cache-miss case, two threads racing on the same key may both construct a + * {@code TopicName} instance, but only one wins the {@code putIfAbsent} and the loser's + * instance is simply discarded. This is safe because {@code TopicName} is immutable and + * construction is cheap compared to the lock-contention / context-switch cost of + * {@code computeIfAbsent}. + */ public static TopicName get(String topic) { + // Fast path: already cached — single volatile read, no lock. TopicName tp = cache.get(topic); if (tp != null) { return tp; } - return cache.computeIfAbsent(topic, k -> new TopicName(k)); + // Slow path: construct outside the bin-lock to avoid blocking other threads. + TopicName newTp = new TopicName(topic); + TopicName existing = cache.putIfAbsent(topic, newTp); + // If another thread raced us and already inserted, use its instance (keeps identity stable). + return existing != null ? existing : newTp; } public static TopicName getPartitionedTopicName(String topic) {