JDK-8184907 : ConcurrentHashMap.computeIfAbsent acquires lock even when key present
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util.concurrent
  • Affected Version: 8,9
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2017-07-07
  • Updated: 2017-07-19
  • Resolved: 2017-07-19
Related Reports
Duplicate :  
Description
FULL PRODUCT VERSION :


ADDITIONAL OS VERSION INFORMATION :
Windows 10

A DESCRIPTION OF THE PROBLEM :
The ConcurrentHashMap.computeIfAbsent always enter into synchornized block regardles if the key is already there this makes it slow

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the  ConcurrentHashMap.computeIfAbsent multiple times with same key, and watch if it enters synchronized block.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Not enter synchronized block if the key is already there in map.
ACTUAL -
It enters teh synchronized block regardles if the key exists or not in the map

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;

public class ConcurrentHashMapTest {
	private final static int numberOfRuns = 1000000;
	private final static int numberOfThreads = Runtime.getRuntime().availableProcessors();
	private final static int keysSize = 1;
	private final static String[] strings = new String[keysSize];
	static {
		for (int n = 0; n < keysSize; n++) {
			strings[n] = "" + (char) ('A' + n);
		}
	}

	public static void main(String[] args) throws InterruptedException {
		for (int n = 0; n < 20; n++) {
			testPutIfAbsent();
			testComputeIfAbsentLambda();
		}
	}

	private static void testPutIfAbsent() throws InterruptedException {
		final AtomicLong totalTime = new AtomicLong();
		final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();
		final Random random = new Random();
		ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);

		for (int i = 0; i < numberOfThreads; i++) {
			executorService.execute(new Runnable() {
				@Override
				public void run() {
					long start, end;
					for (int n = 0; n < numberOfRuns; n++) {
						String s = strings[random.nextInt(strings.length)];
						start = System.nanoTime();

						AtomicInteger count = map.get(s);
						if (count == null) {
							count = new AtomicInteger(0);
							AtomicInteger prevCount = map.putIfAbsent(s, count);
							if (prevCount != null) {
								count = prevCount;
							}
						}
						count.incrementAndGet();
						end = System.nanoTime();
						totalTime.addAndGet(end - start);
					}
				}
			});
		}
		executorService.shutdown();
		executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
		System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
	}

	private static void testComputeIfAbsentLambda() throws InterruptedException {
		final AtomicLong totalTime = new AtomicLong();
		final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<String, AtomicInteger>();
		final Random random = new Random();
		ExecutorService executorService = Executors.newFixedThreadPool(numberOfThreads);
		for (int i = 0; i < numberOfThreads; i++) {
			executorService.execute(new Runnable() {
				@Override
				public void run() {
					long start, end;
					for (int n = 0; n < numberOfRuns; n++) {
						String s = strings[random.nextInt(strings.length)];
						start = System.nanoTime();

						AtomicInteger count = map.computeIfAbsent(s, (k) -> new AtomicInteger(0));
						count.incrementAndGet();

						end = System.nanoTime();
						totalTime.addAndGet(end - start);
					}
				}
			});
		}
		executorService.shutdown();
		executorService.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
		System.out.println("Test " + Thread.currentThread().getStackTrace()[1].getMethodName()
				+ " average time per run: " + (double) totalTime.get() / numberOfThreads / numberOfRuns + " ns");
	}

}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
use putIfAbsent instead

SUPPORT :
YES


Comments
I can confirm that the reporter's supplied benchmark executes much faster in jdk9.
19-07-2017

On running the attached test case, following are the results: Output on JDK 8u131 : Test testPutIfAbsent average time per run: 316.807809 ns Test testComputeIfAbsentLambda average time per run: 1134.93080625 ns Test testPutIfAbsent average time per run: 184.7356575 ns Test testComputeIfAbsentLambda average time per run: 697.5444265 ns Test testPutIfAbsent average time per run: 139.33822925 ns Test testComputeIfAbsentLambda average time per run: 613.67269025 ns Test testPutIfAbsent average time per run: 147.6682415 ns Test testComputeIfAbsentLambda average time per run: 452.35313175 ns Test testPutIfAbsent average time per run: 119.4316395 ns Test testComputeIfAbsentLambda average time per run: 451.598068 ns Test testPutIfAbsent average time per run: 111.199197 ns Test testComputeIfAbsentLambda average time per run: 488.613098 ns Test testPutIfAbsent average time per run: 113.70410875 ns Test testComputeIfAbsentLambda average time per run: 450.7641845 ns Test testPutIfAbsent average time per run: 96.4223665 ns Test testComputeIfAbsentLambda average time per run: 413.7905645 ns Test testPutIfAbsent average time per run: 102.546615 ns Test testComputeIfAbsentLambda average time per run: 479.98598125 ns Test testPutIfAbsent average time per run: 85.1857565 ns Test testComputeIfAbsentLambda average time per run: 424.584756 ns Test testPutIfAbsent average time per run: 124.3381645 ns Test testComputeIfAbsentLambda average time per run: 308.9516385 ns Test testPutIfAbsent average time per run: 94.2570205 ns Test testComputeIfAbsentLambda average time per run: 454.17340625 ns Test testPutIfAbsent average time per run: 100.789694 ns Test testComputeIfAbsentLambda average time per run: 514.0985335 ns Test testPutIfAbsent average time per run: 118.35675275 ns Test testComputeIfAbsentLambda average time per run: 454.8283795 ns Test testPutIfAbsent average time per run: 100.629287 ns Test testComputeIfAbsentLambda average time per run: 435.74485575 ns Test testPutIfAbsent average time per run: 88.14021325 ns Test testComputeIfAbsentLambda average time per run: 469.86056225 ns Test testPutIfAbsent average time per run: 108.65191575 ns Test testComputeIfAbsentLambda average time per run: 397.39341725 ns Test testPutIfAbsent average time per run: 106.68381 ns Test testComputeIfAbsentLambda average time per run: 312.9178635 ns Test testPutIfAbsent average time per run: 106.50693325 ns Test testComputeIfAbsentLambda average time per run: 399.70672325 ns Test testPutIfAbsent average time per run: 101.09124075 ns Test testComputeIfAbsentLambda average time per run: 497.58650275 ns ----------------------------------------------------------------------------------------------------- Output on JDK 9-ea + 178: Test testPutIfAbsent average time per run: 346.58759175 ns Test testComputeIfAbsentLambda average time per run: 296.8174235 ns Test testPutIfAbsent average time per run: 222.225153 ns Test testComputeIfAbsentLambda average time per run: 222.44843525 ns Test testPutIfAbsent average time per run: 171.09287775 ns Test testComputeIfAbsentLambda average time per run: 157.4888535 ns Test testPutIfAbsent average time per run: 155.380435 ns Test testComputeIfAbsentLambda average time per run: 154.87800025 ns Test testPutIfAbsent average time per run: 141.9873025 ns Test testComputeIfAbsentLambda average time per run: 183.03833125 ns Test testPutIfAbsent average time per run: 98.835373 ns Test testComputeIfAbsentLambda average time per run: 101.69353275 ns Test testPutIfAbsent average time per run: 96.00589225 ns Test testComputeIfAbsentLambda average time per run: 114.00941175 ns Test testPutIfAbsent average time per run: 106.2857835 ns Test testComputeIfAbsentLambda average time per run: 116.689941 ns Test testPutIfAbsent average time per run: 110.39539475 ns Test testComputeIfAbsentLambda average time per run: 97.27353725 ns Test testPutIfAbsent average time per run: 105.930796 ns Test testComputeIfAbsentLambda average time per run: 102.61473125 ns Test testPutIfAbsent average time per run: 107.74310325 ns Test testComputeIfAbsentLambda average time per run: 115.17523175 ns Test testPutIfAbsent average time per run: 99.4241815 ns Test testComputeIfAbsentLambda average time per run: 100.11437975 ns Test testPutIfAbsent average time per run: 101.349316 ns Test testComputeIfAbsentLambda average time per run: 104.0551875 ns Test testPutIfAbsent average time per run: 103.60807275 ns Test testComputeIfAbsentLambda average time per run: 105.538104 ns Test testPutIfAbsent average time per run: 123.8868355 ns Test testComputeIfAbsentLambda average time per run: 96.193019 ns Test testPutIfAbsent average time per run: 115.6487795 ns Test testComputeIfAbsentLambda average time per run: 104.32030725 ns Test testPutIfAbsent average time per run: 111.95806375 ns Test testComputeIfAbsentLambda average time per run: 115.63883 ns Test testPutIfAbsent average time per run: 96.120786 ns Test testComputeIfAbsentLambda average time per run: 104.91704125 ns Test testPutIfAbsent average time per run: 106.8231105 ns Test testComputeIfAbsentLambda average time per run: 116.35277275 ns Test testPutIfAbsent average time per run: 100.03930975 ns Test testComputeIfAbsentLambda average time per run: 114.6117435 ns
19-07-2017

Not a client issue. Moving this to core-libs.
19-07-2017