8010293: java/util/concurrent/ConcurrentHashMap/toArray.java fails intermittently

Co-authored-by: Doug Lea <dl@cs.oswego.edu>
Co-authored-by: Peter Levart <peter.levart@gmail.com>
Reviewed-by: forax, chegar, alanb
This commit is contained in:
Paul Sandoz 2013-09-02 11:59:57 +02:00
parent e763c78a79
commit bef51c4a08
2 changed files with 241 additions and 125 deletions

View File

@ -374,27 +374,26 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
* The table is resized when occupancy exceeds a percentage * The table is resized when occupancy exceeds a percentage
* threshold (nominally, 0.75, but see below). Any thread * threshold (nominally, 0.75, but see below). Any thread
* noticing an overfull bin may assist in resizing after the * noticing an overfull bin may assist in resizing after the
* initiating thread allocates and sets up the replacement * initiating thread allocates and sets up the replacement array.
* array. However, rather than stalling, these other threads may * However, rather than stalling, these other threads may proceed
* proceed with insertions etc. The use of TreeBins shields us * with insertions etc. The use of TreeBins shields us from the
* from the worst case effects of overfilling while resizes are in * worst case effects of overfilling while resizes are in
* progress. Resizing proceeds by transferring bins, one by one, * progress. Resizing proceeds by transferring bins, one by one,
* from the table to the next table. To enable concurrency, the * from the table to the next table. However, threads claim small
* next table must be (incrementally) prefilled with place-holders * blocks of indices to transfer (via field transferIndex) before
* serving as reverse forwarders to the old table. Because we are * doing so, reducing contention. Because we are using
* using power-of-two expansion, the elements from each bin must * power-of-two expansion, the elements from each bin must either
* either stay at same index, or move with a power of two * stay at same index, or move with a power of two offset. We
* offset. We eliminate unnecessary node creation by catching * eliminate unnecessary node creation by catching cases where old
* cases where old nodes can be reused because their next fields * nodes can be reused because their next fields won't change. On
* won't change. On average, only about one-sixth of them need * average, only about one-sixth of them need cloning when a table
* cloning when a table doubles. The nodes they replace will be * doubles. The nodes they replace will be garbage collectable as
* garbage collectable as soon as they are no longer referenced by * soon as they are no longer referenced by any reader thread that
* any reader thread that may be in the midst of concurrently * may be in the midst of concurrently traversing table. Upon
* traversing table. Upon transfer, the old table bin contains * transfer, the old table bin contains only a special forwarding
* only a special forwarding node (with hash field "MOVED") that * node (with hash field "MOVED") that contains the next table as
* contains the next table as its key. On encountering a * its key. On encountering a forwarding node, access and update
* forwarding node, access and update operations restart, using * operations restart, using the new table.
* the new table.
* *
* Each bin transfer requires its bin lock, which can stall * Each bin transfer requires its bin lock, which can stall
* waiting for locks while resizing. However, because other * waiting for locks while resizing. However, because other
@ -402,13 +401,19 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
* locks, average aggregate waits become shorter as resizing * locks, average aggregate waits become shorter as resizing
* progresses. The transfer operation must also ensure that all * progresses. The transfer operation must also ensure that all
* accessible bins in both the old and new table are usable by any * accessible bins in both the old and new table are usable by any
* traversal. This is arranged by proceeding from the last bin * traversal. This is arranged in part by proceeding from the
* (table.length - 1) up towards the first. Upon seeing a * last bin (table.length - 1) up towards the first. Upon seeing
* forwarding node, traversals (see class Traverser) arrange to * a forwarding node, traversals (see class Traverser) arrange to
* move to the new table without revisiting nodes. However, to * move to the new table without revisiting nodes. To ensure that
* ensure that no intervening nodes are skipped, bin splitting can * no intervening nodes are skipped even when moved out of order,
* only begin after the associated reverse-forwarders are in * a stack (see class TableStack) is created on first encounter of
* place. * a forwarding node during a traversal, to maintain its place if
* later processing the current table. The need for these
* save/restore mechanics is relatively rare, but when one
* forwarding node is encountered, typically many more will be.
* So Traversers use a simple caching scheme to avoid creating so
* many new TableStack nodes. (Thanks to Peter Levart for
* suggesting use of a stack here.)
* *
* The traversal scheme also applies to partial traversals of * The traversal scheme also applies to partial traversals of
* ranges of bins (via an alternate Traverser constructor) * ranges of bins (via an alternate Traverser constructor)
@ -776,11 +781,6 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
*/ */
private transient volatile int transferIndex; private transient volatile int transferIndex;
/**
* The least available table index to split while resizing.
*/
private transient volatile int transferOrigin;
/** /**
* Spinlock (locked via CAS) used when resizing and/or creating CounterCells. * Spinlock (locked via CAS) used when resizing and/or creating CounterCells.
*/ */
@ -1377,7 +1377,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
} }
int segmentShift = 32 - sshift; int segmentShift = 32 - sshift;
int segmentMask = ssize - 1; int segmentMask = ssize - 1;
@SuppressWarnings("unchecked") Segment<K,V>[] segments = (Segment<K,V>[]) @SuppressWarnings("unchecked")
Segment<K,V>[] segments = (Segment<K,V>[])
new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL]; new Segment<?,?>[DEFAULT_CONCURRENCY_LEVEL];
for (int i = 0; i < segments.length; ++i) for (int i = 0; i < segments.length; ++i)
segments[i] = new Segment<K,V>(LOAD_FACTOR); segments[i] = new Segment<K,V>(LOAD_FACTOR);
@ -1420,8 +1421,10 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
long size = 0L; long size = 0L;
Node<K,V> p = null; Node<K,V> p = null;
for (;;) { for (;;) {
@SuppressWarnings("unchecked") K k = (K) s.readObject(); @SuppressWarnings("unchecked")
@SuppressWarnings("unchecked") V v = (V) s.readObject(); K k = (K) s.readObject();
@SuppressWarnings("unchecked")
V v = (V) s.readObject();
if (k != null && v != null) { if (k != null && v != null) {
p = new Node<K,V>(spread(k.hashCode()), k, v, p); p = new Node<K,V>(spread(k.hashCode()), k, v, p);
++size; ++size;
@ -1439,8 +1442,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
int sz = (int)size; int sz = (int)size;
n = tableSizeFor(sz + (sz >>> 1) + 1); n = tableSizeFor(sz + (sz >>> 1) + 1);
} }
@SuppressWarnings({"rawtypes","unchecked"}) @SuppressWarnings("unchecked")
Node<K,V>[] tab = (Node<K,V>[])new Node[n]; Node<K,V>[] tab = (Node<K,V>[])new Node<?,?>[n];
int mask = n - 1; int mask = n - 1;
long added = 0L; long added = 0L;
while (p != null) { while (p != null) {
@ -2200,8 +2203,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
try { try {
if ((tab = table) == null || tab.length == 0) { if ((tab = table) == null || tab.length == 0) {
int n = (sc > 0) ? sc : DEFAULT_CAPACITY; int n = (sc > 0) ? sc : DEFAULT_CAPACITY;
@SuppressWarnings({"rawtypes","unchecked"}) @SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node[n]; Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = tab = nt; table = tab = nt;
sc = n - (n >>> 2); sc = n - (n >>> 2);
} }
@ -2246,7 +2249,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
while (s >= (long)(sc = sizeCtl) && (tab = table) != null && while (s >= (long)(sc = sizeCtl) && (tab = table) != null &&
tab.length < MAXIMUM_CAPACITY) { tab.length < MAXIMUM_CAPACITY) {
if (sc < 0) { if (sc < 0) {
if (sc == -1 || transferIndex <= transferOrigin || if (sc == -1 || transferIndex <= 0 ||
(nt = nextTable) == null) (nt = nextTable) == null)
break; break;
if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1))
@ -2266,10 +2269,13 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
Node<K,V>[] nextTab; int sc; Node<K,V>[] nextTab; int sc;
if ((f instanceof ForwardingNode) && if ((f instanceof ForwardingNode) &&
(nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) { (nextTab = ((ForwardingNode<K,V>)f).nextTable) != null) {
if (nextTab == nextTable && tab == table && while (transferIndex > 0 && nextTab == nextTable &&
transferIndex > transferOrigin && (sc = sizeCtl) < -1 && (sc = sizeCtl) < -1) {
U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) if (U.compareAndSwapInt(this, SIZECTL, sc, sc - 1)) {
transfer(tab, nextTab); transfer(tab, nextTab);
break;
}
}
return nextTab; return nextTab;
} }
return table; return table;
@ -2291,8 +2297,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) { if (U.compareAndSwapInt(this, SIZECTL, sc, -1)) {
try { try {
if (table == tab) { if (table == tab) {
@SuppressWarnings({"rawtypes","unchecked"}) @SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node[n]; Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n];
table = nt; table = nt;
sc = n - (n >>> 2); sc = n - (n >>> 2);
} }
@ -2319,36 +2325,27 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
stride = MIN_TRANSFER_STRIDE; // subdivide range stride = MIN_TRANSFER_STRIDE; // subdivide range
if (nextTab == null) { // initiating if (nextTab == null) { // initiating
try { try {
@SuppressWarnings({"rawtypes","unchecked"}) @SuppressWarnings("unchecked")
Node<K,V>[] nt = (Node<K,V>[])new Node[n << 1]; Node<K,V>[] nt = (Node<K,V>[])new Node<?,?>[n << 1];
nextTab = nt; nextTab = nt;
} catch (Throwable ex) { // try to cope with OOME } catch (Throwable ex) { // try to cope with OOME
sizeCtl = Integer.MAX_VALUE; sizeCtl = Integer.MAX_VALUE;
return; return;
} }
nextTable = nextTab; nextTable = nextTab;
transferOrigin = n;
transferIndex = n; transferIndex = n;
ForwardingNode<K,V> rev = new ForwardingNode<K,V>(tab);
for (int k = n; k > 0;) { // progressively reveal ready slots
int nextk = (k > stride) ? k - stride : 0;
for (int m = nextk; m < k; ++m)
nextTab[m] = rev;
for (int m = n + nextk; m < n + k; ++m)
nextTab[m] = rev;
U.putOrderedInt(this, TRANSFERORIGIN, k = nextk);
}
} }
int nextn = nextTab.length; int nextn = nextTab.length;
ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab); ForwardingNode<K,V> fwd = new ForwardingNode<K,V>(nextTab);
boolean advance = true; boolean advance = true;
boolean finishing = false; // to ensure sweep before committing nextTab boolean finishing = false; // to ensure sweep before committing nextTab
for (int i = 0, bound = 0;;) { for (int i = 0, bound = 0;;) {
int nextIndex, nextBound, fh; Node<K,V> f; Node<K,V> f; int fh;
while (advance) { while (advance) {
int nextIndex, nextBound;
if (--i >= bound || finishing) if (--i >= bound || finishing)
advance = false; advance = false;
else if ((nextIndex = transferIndex) <= transferOrigin) { else if ((nextIndex = transferIndex) <= 0) {
i = -1; i = -1;
advance = false; advance = false;
} }
@ -2362,29 +2359,22 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
} }
} }
if (i < 0 || i >= n || i + n >= nextn) { if (i < 0 || i >= n || i + n >= nextn) {
int sc;
if (finishing) { if (finishing) {
nextTable = null; nextTable = null;
table = nextTab; table = nextTab;
sizeCtl = (n << 1) - (n >>> 1); sizeCtl = (n << 1) - (n >>> 1);
return; return;
} }
for (int sc;;) { if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) {
if (U.compareAndSwapInt(this, SIZECTL, sc = sizeCtl, ++sc)) { if (sc != -1)
if (sc != -1) return;
return; finishing = advance = true;
finishing = advance = true; i = n; // recheck before commit
i = n; // recheck before commit
break;
}
}
}
else if ((f = tabAt(tab, i)) == null) {
if (casTabAt(tab, i, null, fwd)) {
setTabAt(nextTab, i, null);
setTabAt(nextTab, i + n, null);
advance = true;
} }
} }
else if ((f = tabAt(tab, i)) == null)
advance = casTabAt(tab, i, null, fwd);
else if ((fh = f.hash) == MOVED) else if ((fh = f.hash) == MOVED)
advance = true; // already processed advance = true; // already processed
else { else {
@ -3223,6 +3213,18 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
/* ----------------Table Traversal -------------- */ /* ----------------Table Traversal -------------- */
/**
* Records the table, its length, and current traversal index for a
* traverser that must process a region of a forwarded table before
* proceeding with current table.
*/
static final class TableStack<K,V> {
int length;
int index;
Node<K,V>[] tab;
TableStack<K,V> next;
}
/** /**
* Encapsulates traversal for methods such as containsValue; also * Encapsulates traversal for methods such as containsValue; also
* serves as a base class for other iterators and spliterators. * serves as a base class for other iterators and spliterators.
@ -3247,6 +3249,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
static class Traverser<K,V> { static class Traverser<K,V> {
Node<K,V>[] tab; // current table; updated if resized Node<K,V>[] tab; // current table; updated if resized
Node<K,V> next; // the next entry to use Node<K,V> next; // the next entry to use
TableStack<K,V> stack, spare; // to save/restore on ForwardingNodes
int index; // index of bin to use next int index; // index of bin to use next
int baseIndex; // current index of initial table int baseIndex; // current index of initial table
int baseLimit; // index bound for initial table int baseLimit; // index bound for initial table
@ -3268,16 +3271,17 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
if ((e = next) != null) if ((e = next) != null)
e = e.next; e = e.next;
for (;;) { for (;;) {
Node<K,V>[] t; int i, n; K ek; // must use locals in checks Node<K,V>[] t; int i, n; // must use locals in checks
if (e != null) if (e != null)
return next = e; return next = e;
if (baseIndex >= baseLimit || (t = tab) == null || if (baseIndex >= baseLimit || (t = tab) == null ||
(n = t.length) <= (i = index) || i < 0) (n = t.length) <= (i = index) || i < 0)
return next = null; return next = null;
if ((e = tabAt(t, index)) != null && e.hash < 0) { if ((e = tabAt(t, i)) != null && e.hash < 0) {
if (e instanceof ForwardingNode) { if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable; tab = ((ForwardingNode<K,V>)e).nextTable;
e = null; e = null;
pushState(t, i, n);
continue; continue;
} }
else if (e instanceof TreeBin) else if (e instanceof TreeBin)
@ -3285,10 +3289,49 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
else else
e = null; e = null;
} }
if ((index += baseSize) >= n) if (stack != null)
index = ++baseIndex; // visit upper slots if present recoverState(n);
else if ((index = i + baseSize) >= n)
index = ++baseIndex; // visit upper slots if present
} }
} }
/**
* Saves traversal state upon encountering a forwarding node.
*/
private void pushState(Node<K,V>[] t, int i, int n) {
TableStack<K,V> s = spare; // reuse if possible
if (s != null)
spare = s.next;
else
s = new TableStack<K,V>();
s.tab = t;
s.length = n;
s.index = i;
s.next = stack;
stack = s;
}
/**
* Possibly pops traversal state.
*
* @param n length of current table
*/
private void recoverState(int n) {
TableStack<K,V> s; int len;
while ((s = stack) != null && (index += (len = s.length)) >= n) {
n = len;
index = s.index;
tab = s.tab;
s.tab = null;
TableStack<K,V> next = s.next;
s.next = spare; // save for reuse
stack = next;
spare = s;
}
if (s == null && (index += baseSize) >= n)
index = ++baseIndex;
}
} }
/** /**
@ -4722,6 +4765,7 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
abstract static class BulkTask<K,V,R> extends CountedCompleter<R> { abstract static class BulkTask<K,V,R> extends CountedCompleter<R> {
Node<K,V>[] tab; // same as Traverser Node<K,V>[] tab; // same as Traverser
Node<K,V> next; Node<K,V> next;
TableStack<K,V> stack, spare;
int index; int index;
int baseIndex; int baseIndex;
int baseLimit; int baseLimit;
@ -4750,16 +4794,17 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
if ((e = next) != null) if ((e = next) != null)
e = e.next; e = e.next;
for (;;) { for (;;) {
Node<K,V>[] t; int i, n; K ek; // must use locals in checks Node<K,V>[] t; int i, n;
if (e != null) if (e != null)
return next = e; return next = e;
if (baseIndex >= baseLimit || (t = tab) == null || if (baseIndex >= baseLimit || (t = tab) == null ||
(n = t.length) <= (i = index) || i < 0) (n = t.length) <= (i = index) || i < 0)
return next = null; return next = null;
if ((e = tabAt(t, index)) != null && e.hash < 0) { if ((e = tabAt(t, i)) != null && e.hash < 0) {
if (e instanceof ForwardingNode) { if (e instanceof ForwardingNode) {
tab = ((ForwardingNode<K,V>)e).nextTable; tab = ((ForwardingNode<K,V>)e).nextTable;
e = null; e = null;
pushState(t, i, n);
continue; continue;
} }
else if (e instanceof TreeBin) else if (e instanceof TreeBin)
@ -4767,10 +4812,41 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
else else
e = null; e = null;
} }
if ((index += baseSize) >= n) if (stack != null)
index = ++baseIndex; // visit upper slots if present recoverState(n);
else if ((index = i + baseSize) >= n)
index = ++baseIndex;
} }
} }
private void pushState(Node<K,V>[] t, int i, int n) {
TableStack<K,V> s = spare;
if (s != null)
spare = s.next;
else
s = new TableStack<K,V>();
s.tab = t;
s.length = n;
s.index = i;
s.next = stack;
stack = s;
}
private void recoverState(int n) {
TableStack<K,V> s; int len;
while ((s = stack) != null && (index += (len = s.length)) >= n) {
n = len;
index = s.index;
tab = s.tab;
s.tab = null;
TableStack<K,V> next = s.next;
s.next = spare; // save for reuse
stack = next;
spare = s;
}
if (s == null && (index += baseSize) >= n)
index = ++baseIndex;
}
} }
/* /*
@ -5229,7 +5305,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") ReduceKeysTask<K,V> @SuppressWarnings("unchecked")
ReduceKeysTask<K,V>
t = (ReduceKeysTask<K,V>)c, t = (ReduceKeysTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5276,7 +5353,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") ReduceValuesTask<K,V> @SuppressWarnings("unchecked")
ReduceValuesTask<K,V>
t = (ReduceValuesTask<K,V>)c, t = (ReduceValuesTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5321,7 +5399,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") ReduceEntriesTask<K,V> @SuppressWarnings("unchecked")
ReduceEntriesTask<K,V>
t = (ReduceEntriesTask<K,V>)c, t = (ReduceEntriesTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5374,7 +5453,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceKeysTask<K,V,U> @SuppressWarnings("unchecked")
MapReduceKeysTask<K,V,U>
t = (MapReduceKeysTask<K,V,U>)c, t = (MapReduceKeysTask<K,V,U>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5427,7 +5507,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceValuesTask<K,V,U> @SuppressWarnings("unchecked")
MapReduceValuesTask<K,V,U>
t = (MapReduceValuesTask<K,V,U>)c, t = (MapReduceValuesTask<K,V,U>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5480,7 +5561,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceEntriesTask<K,V,U> @SuppressWarnings("unchecked")
MapReduceEntriesTask<K,V,U>
t = (MapReduceEntriesTask<K,V,U>)c, t = (MapReduceEntriesTask<K,V,U>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5533,7 +5615,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceMappingsTask<K,V,U> @SuppressWarnings("unchecked")
MapReduceMappingsTask<K,V,U>
t = (MapReduceMappingsTask<K,V,U>)c, t = (MapReduceMappingsTask<K,V,U>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5585,7 +5668,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceKeysToDoubleTask<K,V> @SuppressWarnings("unchecked")
MapReduceKeysToDoubleTask<K,V>
t = (MapReduceKeysToDoubleTask<K,V>)c, t = (MapReduceKeysToDoubleTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5634,7 +5718,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceValuesToDoubleTask<K,V> @SuppressWarnings("unchecked")
MapReduceValuesToDoubleTask<K,V>
t = (MapReduceValuesToDoubleTask<K,V>)c, t = (MapReduceValuesToDoubleTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5683,7 +5768,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceEntriesToDoubleTask<K,V> @SuppressWarnings("unchecked")
MapReduceEntriesToDoubleTask<K,V>
t = (MapReduceEntriesToDoubleTask<K,V>)c, t = (MapReduceEntriesToDoubleTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5732,7 +5818,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceMappingsToDoubleTask<K,V> @SuppressWarnings("unchecked")
MapReduceMappingsToDoubleTask<K,V>
t = (MapReduceMappingsToDoubleTask<K,V>)c, t = (MapReduceMappingsToDoubleTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5781,7 +5868,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceKeysToLongTask<K,V> @SuppressWarnings("unchecked")
MapReduceKeysToLongTask<K,V>
t = (MapReduceKeysToLongTask<K,V>)c, t = (MapReduceKeysToLongTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5830,7 +5918,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceValuesToLongTask<K,V> @SuppressWarnings("unchecked")
MapReduceValuesToLongTask<K,V>
t = (MapReduceValuesToLongTask<K,V>)c, t = (MapReduceValuesToLongTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5879,7 +5968,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceEntriesToLongTask<K,V> @SuppressWarnings("unchecked")
MapReduceEntriesToLongTask<K,V>
t = (MapReduceEntriesToLongTask<K,V>)c, t = (MapReduceEntriesToLongTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5928,7 +6018,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceMappingsToLongTask<K,V> @SuppressWarnings("unchecked")
MapReduceMappingsToLongTask<K,V>
t = (MapReduceMappingsToLongTask<K,V>)c, t = (MapReduceMappingsToLongTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -5977,7 +6068,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceKeysToIntTask<K,V> @SuppressWarnings("unchecked")
MapReduceKeysToIntTask<K,V>
t = (MapReduceKeysToIntTask<K,V>)c, t = (MapReduceKeysToIntTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -6026,7 +6118,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceValuesToIntTask<K,V> @SuppressWarnings("unchecked")
MapReduceValuesToIntTask<K,V>
t = (MapReduceValuesToIntTask<K,V>)c, t = (MapReduceValuesToIntTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -6075,7 +6168,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceEntriesToIntTask<K,V> @SuppressWarnings("unchecked")
MapReduceEntriesToIntTask<K,V>
t = (MapReduceEntriesToIntTask<K,V>)c, t = (MapReduceEntriesToIntTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -6124,7 +6218,8 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
result = r; result = r;
CountedCompleter<?> c; CountedCompleter<?> c;
for (c = firstComplete(); c != null; c = c.nextComplete()) { for (c = firstComplete(); c != null; c = c.nextComplete()) {
@SuppressWarnings("unchecked") MapReduceMappingsToIntTask<K,V> @SuppressWarnings("unchecked")
MapReduceMappingsToIntTask<K,V>
t = (MapReduceMappingsToIntTask<K,V>)c, t = (MapReduceMappingsToIntTask<K,V>)c,
s = t.rights; s = t.rights;
while (s != null) { while (s != null) {
@ -6140,7 +6235,6 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
private static final sun.misc.Unsafe U; private static final sun.misc.Unsafe U;
private static final long SIZECTL; private static final long SIZECTL;
private static final long TRANSFERINDEX; private static final long TRANSFERINDEX;
private static final long TRANSFERORIGIN;
private static final long BASECOUNT; private static final long BASECOUNT;
private static final long CELLSBUSY; private static final long CELLSBUSY;
private static final long CELLVALUE; private static final long CELLVALUE;
@ -6155,8 +6249,6 @@ public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>
(k.getDeclaredField("sizeCtl")); (k.getDeclaredField("sizeCtl"));
TRANSFERINDEX = U.objectFieldOffset TRANSFERINDEX = U.objectFieldOffset
(k.getDeclaredField("transferIndex")); (k.getDeclaredField("transferIndex"));
TRANSFERORIGIN = U.objectFieldOffset
(k.getDeclaredField("transferOrigin"));
BASECOUNT = U.objectFieldOffset BASECOUNT = U.objectFieldOffset
(k.getDeclaredField("baseCount")); (k.getDeclaredField("baseCount"));
CELLSBUSY = U.objectFieldOffset CELLSBUSY = U.objectFieldOffset

View File

@ -23,39 +23,53 @@
/* /*
* @test * @test
* @bug 4486658 * @bug 4486658 8010293
* @summary thread safety of toArray methods of subCollections * @summary thread safety of toArray methods of subCollections
* @author Martin Buchholz * @author Martin Buchholz
*/ */
import java.util.*; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.*; import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.IntStream;
public class toArray { public class ToArray {
public static void main(String[] args) throws Throwable { public static void main(String[] args) throws Throwable {
// Execute a number of times to increase the probability of
// failure if there is an issue
for (int i = 0; i < 16; i++) {
executeTest();
}
}
static void executeTest() throws Throwable {
final Throwable throwable[] = new Throwable[1]; final Throwable throwable[] = new Throwable[1];
final int maxSize = 1000; final ConcurrentHashMap<Integer, Integer> m = new ConcurrentHashMap<>();
final ConcurrentHashMap<Integer, Integer> m
= new ConcurrentHashMap<Integer, Integer>();
final Thread t1 = new Thread() { public void run() { // Number of workers equal to the number of processors
for (int i = 0; i < maxSize; i++) // Each worker will put globally unique keys into the map
m.put(i,i);}}; final int nWorkers = Runtime.getRuntime().availableProcessors();
final int sizePerWorker = 1024;
final int maxSize = nWorkers * sizePerWorker;
final Thread t2 = new Thread() { // The foreman keeps checking that the size of the arrays
public Throwable exception = null; // obtained from the key and value sets is never less than the
// previously observed size and is never greater than the maximum size
// NOTE: these size constraints are not specific to toArray and are
// applicable to any form of traversal of the collection views
CompletableFuture<?> foreman = CompletableFuture.runAsync(new Runnable() {
private int prevSize = 0; private int prevSize = 0;
private boolean checkProgress(Object[] a) { private boolean checkProgress(Object[] a) {
int size = a.length; int size = a.length;
if (size < prevSize) throw new RuntimeException("WRONG WAY"); if (size < prevSize) throw new RuntimeException("WRONG WAY");
if (size > maxSize) throw new RuntimeException("OVERSHOOT"); if (size > maxSize) throw new RuntimeException("OVERSHOOT");
if (size == maxSize) return true; if (size == maxSize) return true;
prevSize = size; prevSize = size;
return false; return false;
} }
@Override
public void run() { public void run() {
try { try {
Integer[] empty = new Integer[0]; Integer[] empty = new Integer[0];
@ -65,15 +79,25 @@ public class toArray {
if (checkProgress(m.values().toArray(empty))) return; if (checkProgress(m.values().toArray(empty))) return;
if (checkProgress(m.keySet().toArray(empty))) return; if (checkProgress(m.keySet().toArray(empty))) return;
} }
} catch (Throwable t) { }
throwable[0] = t; catch (Throwable t) {
}}}; throwable[0] = t;
}
}
});
t2.start(); // Create workers
t1.start(); // Each worker will put globally unique keys into the map
CompletableFuture<?>[] workers = IntStream.range(0, nWorkers).
mapToObj(w -> CompletableFuture.runAsync(() -> {
for (int i = 0, o = w * sizePerWorker; i < sizePerWorker; i++)
m.put(o + i, i);
})).
toArray(CompletableFuture<?>[]::new);
t1.join(); // Wait for workers and then foreman to complete
t2.join(); CompletableFuture.allOf(workers).join();
foreman.join();
if (throwable[0] != null) if (throwable[0] != null)
throw throwable[0]; throw throwable[0];