8202422: value of 'sizeCtl' in ConcurrentHashMap varies with the constructor called

Reviewed-by: martin, psandoz
This commit is contained in:
Doug Lea 2018-06-25 09:59:16 -07:00
parent 7ca4027957
commit abffccb329
2 changed files with 64 additions and 23 deletions

View File

@ -702,12 +702,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
* See Hackers Delight, sec 3.2
*/
private static final int tableSizeFor(int c) {
int n = c - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
int n = -1 >>> Integer.numberOfLeadingZeros(c - 1);
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
@ -844,12 +839,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
* elements is negative
*/
public ConcurrentHashMap(int initialCapacity) {
if (initialCapacity < 0)
throw new IllegalArgumentException();
int cap = ((initialCapacity >= (MAXIMUM_CAPACITY >>> 1)) ?
MAXIMUM_CAPACITY :
tableSizeFor(initialCapacity + (initialCapacity >>> 1) + 1));
this.sizeCtl = cap;
this(initialCapacity, LOAD_FACTOR, 1);
}
/**
@ -883,8 +873,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
/**
* Creates a new, empty map with an initial table size based on
* the given number of elements ({@code initialCapacity}), table
* density ({@code loadFactor}), and number of concurrently
* the given number of elements ({@code initialCapacity}), initial
* table density ({@code loadFactor}), and number of concurrently
* updating threads ({@code concurrencyLevel}).
*
* @param initialCapacity the initial capacity. The implementation
@ -1473,13 +1463,9 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
if (size == 0L)
sizeCtl = 0;
else {
int n;
if (size >= (long)(MAXIMUM_CAPACITY >>> 1))
n = MAXIMUM_CAPACITY;
else {
int sz = (int)size;
n = tableSizeFor(sz + (sz >>> 1) + 1);
}
long ts = (long)(1.0 + size / LOAD_FACTOR);
int n = (ts >= (long)MAXIMUM_CAPACITY) ?
MAXIMUM_CAPACITY : tableSizeFor((int)ts);
@SuppressWarnings("unchecked")
Node<K,V>[] tab = (Node<K,V>[])new Node<?,?>[n];
int mask = n - 1;

View File

@ -45,15 +45,20 @@ import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.invoke.VarHandle;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
@Test
public class WhiteBox {
final ThreadLocalRandom rnd = ThreadLocalRandom.current();
final VarHandle TABLE, NEXTTABLE, SIZECTL;
final MethodHandle TABLE_SIZE_FOR;
WhiteBox() throws ReflectiveOperationException {
Class<?> mClass = ConcurrentHashMap.class;
@ -65,11 +70,33 @@ public class WhiteBox {
TABLE = lookup.findVarHandle(mClass, "table", nodeArrayClass);
NEXTTABLE = lookup.findVarHandle(mClass, "nextTable", nodeArrayClass);
SIZECTL = lookup.findVarHandle(mClass, "sizeCtl", int.class);
TABLE_SIZE_FOR = lookup.findStatic(
mClass, "tableSizeFor",
MethodType.methodType(int.class, int.class));
}
Object[] table(ConcurrentHashMap m) { return (Object[]) TABLE.getVolatile(m); }
Object[] nextTable(ConcurrentHashMap m) { return (Object[]) NEXTTABLE.getVolatile(m); }
int sizeCtl(ConcurrentHashMap m) { return (int) SIZECTL.getVolatile(m); }
int tableSizeFor(int n) {
try {
return (int) TABLE_SIZE_FOR.invoke(n);
} catch (Throwable t) { throw new AssertionError(t); }
}
List<Supplier<ConcurrentHashMap>> newConcurrentHashMapSuppliers(
int initialCapacity) {
return List.of(
() -> new ConcurrentHashMap(initialCapacity),
() -> new ConcurrentHashMap(initialCapacity, 0.75f),
() -> new ConcurrentHashMap(initialCapacity, 0.75f, 1));
}
ConcurrentHashMap newConcurrentHashMap(int initialCapacity) {
List<Supplier<ConcurrentHashMap>> suppliers
= newConcurrentHashMapSuppliers(initialCapacity);
return suppliers.get(rnd.nextInt(suppliers.size())).get();
}
@Test
public void defaultConstructor() {
@ -83,7 +110,7 @@ public class WhiteBox {
public void shouldNotResizeWhenInitialCapacityProvided() {
int initialCapacity = rnd.nextInt(1, 100);
Object[] initialTable = null;
ConcurrentHashMap m = new ConcurrentHashMap(initialCapacity);
ConcurrentHashMap m = newConcurrentHashMap(initialCapacity);
// table is lazily initialized
assertNull(table(m));
@ -101,6 +128,34 @@ public class WhiteBox {
assertEquals(initialTable.length, expectedInitialTableLength);
}
@Test
public void constructorsShouldGiveSameInitialCapacity() {
int initialCapacity = rnd.nextInt(1, 256);
long distinctTableLengths
= newConcurrentHashMapSuppliers(initialCapacity).stream()
.map(Supplier::get)
.mapToInt(map -> { map.put(42, 42); return table(map).length; })
.distinct()
.count();
assertEquals(1L, distinctTableLengths);
}
@Test
public void testTableSizeFor() {
assertEquals(1, tableSizeFor(0));
assertEquals(1, tableSizeFor(1));
assertEquals(2, tableSizeFor(2));
assertEquals(4, tableSizeFor(3));
assertEquals(16, tableSizeFor(15));
assertEquals(16, tableSizeFor(16));
assertEquals(32, tableSizeFor(17));
int maxSize = 1 << 30;
assertEquals(maxSize, tableSizeFor(maxSize - 1));
assertEquals(maxSize, tableSizeFor(maxSize));
assertEquals(maxSize, tableSizeFor(maxSize + 1));
assertEquals(maxSize, tableSizeFor(Integer.MAX_VALUE));
}
byte[] serialBytes(Object o) {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -132,7 +187,7 @@ public class WhiteBox {
public void testSerialization() {
assertInvariants(serialClone(new ConcurrentHashMap()));
ConcurrentHashMap m = new ConcurrentHashMap(rnd.nextInt(100));
ConcurrentHashMap m = newConcurrentHashMap(rnd.nextInt(100));
m.put(1, 1);
ConcurrentHashMap clone = serialClone(m);
assertInvariants(clone);