Merge "Address static cache access issues."
This commit is contained in:
committed by
Android (Google) Code Review
commit
2a6df07afa
@@ -48,6 +48,8 @@ import java.util.Set;
|
||||
* you have no control over this shrinking -- if you set a capacity and then remove an
|
||||
* item, it may reduce the capacity to better match the current size. In the future an
|
||||
* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
|
||||
*
|
||||
* <p>This structure is <b>NOT</b> thread-safe.</p>
|
||||
*/
|
||||
public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
private static final boolean DEBUG = false;
|
||||
@@ -103,15 +105,21 @@ public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
static Object[] mTwiceBaseCache;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
|
||||
static int mTwiceBaseCacheSize;
|
||||
/**
|
||||
* Separate locks for each cache since each can be accessed independently of the other without
|
||||
* risk of a deadlock.
|
||||
*/
|
||||
private static final Object sBaseCacheLock = new Object();
|
||||
private static final Object sTwiceBaseCacheLock = new Object();
|
||||
|
||||
final boolean mIdentityHashCode;
|
||||
private final boolean mIdentityHashCode;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public key/value API.
|
||||
int[] mHashes;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public key/value API.
|
||||
Object[] mArray;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
|
||||
int mSize;
|
||||
MapCollections<K, V> mCollections;
|
||||
private MapCollections<K, V> mCollections;
|
||||
|
||||
private static int binarySearchHashes(int[] hashes, int N, int hash) {
|
||||
try {
|
||||
@@ -209,31 +217,57 @@ public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
throw new UnsupportedOperationException("ArrayMap is immutable");
|
||||
}
|
||||
if (size == (BASE_SIZE*2)) {
|
||||
synchronized (ArrayMap.class) {
|
||||
synchronized (sTwiceBaseCacheLock) {
|
||||
if (mTwiceBaseCache != null) {
|
||||
final Object[] array = mTwiceBaseCache;
|
||||
mArray = array;
|
||||
mTwiceBaseCache = (Object[])array[0];
|
||||
mHashes = (int[])array[1];
|
||||
array[0] = array[1] = null;
|
||||
mTwiceBaseCacheSize--;
|
||||
if (DEBUG) Log.d(TAG, "Retrieving 2x cache " + mHashes
|
||||
+ " now have " + mTwiceBaseCacheSize + " entries");
|
||||
return;
|
||||
try {
|
||||
mTwiceBaseCache = (Object[]) array[0];
|
||||
mHashes = (int[]) array[1];
|
||||
if (mHashes != null) {
|
||||
array[0] = array[1] = null;
|
||||
mTwiceBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 2x cache " + mHashes
|
||||
+ " now have " + mTwiceBaseCacheSize + " entries");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
// Whoops! Someone trampled the array (probably due to not protecting
|
||||
// their access with a lock). Our cache is corrupt; report and give up.
|
||||
Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
|
||||
+ " [1]=" + array[1]);
|
||||
mTwiceBaseCache = null;
|
||||
mTwiceBaseCacheSize = 0;
|
||||
}
|
||||
}
|
||||
} else if (size == BASE_SIZE) {
|
||||
synchronized (ArrayMap.class) {
|
||||
synchronized (sBaseCacheLock) {
|
||||
if (mBaseCache != null) {
|
||||
final Object[] array = mBaseCache;
|
||||
mArray = array;
|
||||
mBaseCache = (Object[])array[0];
|
||||
mHashes = (int[])array[1];
|
||||
array[0] = array[1] = null;
|
||||
mBaseCacheSize--;
|
||||
if (DEBUG) Log.d(TAG, "Retrieving 1x cache " + mHashes
|
||||
+ " now have " + mBaseCacheSize + " entries");
|
||||
return;
|
||||
try {
|
||||
mBaseCache = (Object[]) array[0];
|
||||
mHashes = (int[]) array[1];
|
||||
if (mHashes != null) {
|
||||
array[0] = array[1] = null;
|
||||
mBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 1x cache " + mHashes
|
||||
+ " now have " + mBaseCacheSize + " entries");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
// Whoops! Someone trampled the array (probably due to not protecting
|
||||
// their access with a lock). Our cache is corrupt; report and give up.
|
||||
Slog.wtf(TAG, "Found corrupt ArrayMap cache: [0]=" + array[0]
|
||||
+ " [1]=" + array[1]);
|
||||
mBaseCache = null;
|
||||
mBaseCacheSize = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,10 +276,14 @@ public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
mArray = new Object[size<<1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
|
||||
* words, don't pass mHashes or mArray in directly.
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
|
||||
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
|
||||
if (hashes.length == (BASE_SIZE*2)) {
|
||||
synchronized (ArrayMap.class) {
|
||||
synchronized (sTwiceBaseCacheLock) {
|
||||
if (mTwiceBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = mTwiceBaseCache;
|
||||
array[1] = hashes;
|
||||
@@ -259,7 +297,7 @@ public final class ArrayMap<K, V> implements Map<K, V> {
|
||||
}
|
||||
}
|
||||
} else if (hashes.length == BASE_SIZE) {
|
||||
synchronized (ArrayMap.class) {
|
||||
synchronized (sBaseCacheLock) {
|
||||
if (mBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = mBaseCache;
|
||||
array[1] = hashes;
|
||||
|
||||
@@ -23,6 +23,7 @@ import libcore.util.EmptyArray;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.util.Collection;
|
||||
import java.util.ConcurrentModificationException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -46,6 +47,8 @@ import java.util.function.Predicate;
|
||||
* you have no control over this shrinking -- if you set a capacity and then remove an
|
||||
* item, it may reduce the capacity to better match the current size. In the future an
|
||||
* explicit call to set the capacity should turn off this aggressive shrinking behavior.</p>
|
||||
*
|
||||
* <p>This structure is <b>NOT</b> thread-safe.</p>
|
||||
*/
|
||||
public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
private static final boolean DEBUG = false;
|
||||
@@ -72,15 +75,30 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
static int sBaseCacheSize;
|
||||
static Object[] sTwiceBaseCache;
|
||||
static int sTwiceBaseCacheSize;
|
||||
/**
|
||||
* Separate locks for each cache since each can be accessed independently of the other without
|
||||
* risk of a deadlock.
|
||||
*/
|
||||
private static final Object sBaseCacheLock = new Object();
|
||||
private static final Object sTwiceBaseCacheLock = new Object();
|
||||
|
||||
final boolean mIdentityHashCode;
|
||||
private final boolean mIdentityHashCode;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use public API.
|
||||
int[] mHashes;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Storage is an implementation detail. Use public API.
|
||||
Object[] mArray;
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Use size()
|
||||
int mSize;
|
||||
MapCollections<E, E> mCollections;
|
||||
private MapCollections<E, E> mCollections;
|
||||
|
||||
private int binarySearch(int[] hashes, int hash) {
|
||||
try {
|
||||
return ContainerHelpers.binarySearch(hashes, mSize, hash);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Hashes are an implementation detail. Use indexOfKey(Object).
|
||||
private int indexOf(Object key, int hash) {
|
||||
@@ -91,7 +109,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
return ~0;
|
||||
}
|
||||
|
||||
int index = ContainerHelpers.binarySearch(mHashes, N, hash);
|
||||
int index = binarySearch(mHashes, hash);
|
||||
|
||||
// If the hash code wasn't found, then we have no entry for this key.
|
||||
if (index < 0) {
|
||||
@@ -130,7 +148,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
return ~0;
|
||||
}
|
||||
|
||||
int index = ContainerHelpers.binarySearch(mHashes, N, 0);
|
||||
int index = binarySearch(mHashes, 0);
|
||||
|
||||
// If the hash code wasn't found, then we have no entry for this key.
|
||||
if (index < 0) {
|
||||
@@ -163,20 +181,22 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
|
||||
private void allocArrays(final int size) {
|
||||
if (size == (BASE_SIZE * 2)) {
|
||||
synchronized (ArraySet.class) {
|
||||
synchronized (sTwiceBaseCacheLock) {
|
||||
if (sTwiceBaseCache != null) {
|
||||
final Object[] array = sTwiceBaseCache;
|
||||
try {
|
||||
mArray = array;
|
||||
sTwiceBaseCache = (Object[]) array[0];
|
||||
mHashes = (int[]) array[1];
|
||||
array[0] = array[1] = null;
|
||||
sTwiceBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
|
||||
+ sTwiceBaseCacheSize + " entries");
|
||||
}
|
||||
return;
|
||||
if (mHashes != null) {
|
||||
array[0] = array[1] = null;
|
||||
sTwiceBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 2x cache " + mHashes + " now have "
|
||||
+ sTwiceBaseCacheSize + " entries");
|
||||
}
|
||||
return;
|
||||
}
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
// Whoops! Someone trampled the array (probably due to not protecting
|
||||
@@ -188,20 +208,22 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
}
|
||||
}
|
||||
} else if (size == BASE_SIZE) {
|
||||
synchronized (ArraySet.class) {
|
||||
synchronized (sBaseCacheLock) {
|
||||
if (sBaseCache != null) {
|
||||
final Object[] array = sBaseCache;
|
||||
try {
|
||||
mArray = array;
|
||||
sBaseCache = (Object[]) array[0];
|
||||
mHashes = (int[]) array[1];
|
||||
array[0] = array[1] = null;
|
||||
sBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have " + sBaseCacheSize
|
||||
+ " entries");
|
||||
if (mHashes != null) {
|
||||
array[0] = array[1] = null;
|
||||
sBaseCacheSize--;
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "Retrieving 1x cache " + mHashes + " now have "
|
||||
+ sBaseCacheSize + " entries");
|
||||
}
|
||||
return;
|
||||
}
|
||||
return;
|
||||
} catch (ClassCastException e) {
|
||||
}
|
||||
// Whoops! Someone trampled the array (probably due to not protecting
|
||||
@@ -218,10 +240,14 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
mArray = new Object[size];
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure <b>NOT</b> to call this method with arrays that can still be modified. In other
|
||||
* words, don't pass mHashes or mArray in directly.
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = 28) // Allocations are an implementation detail.
|
||||
private static void freeArrays(final int[] hashes, final Object[] array, final int size) {
|
||||
if (hashes.length == (BASE_SIZE * 2)) {
|
||||
synchronized (ArraySet.class) {
|
||||
synchronized (sTwiceBaseCacheLock) {
|
||||
if (sTwiceBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = sTwiceBaseCache;
|
||||
array[1] = hashes;
|
||||
@@ -237,7 +263,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
}
|
||||
}
|
||||
} else if (hashes.length == BASE_SIZE) {
|
||||
synchronized (ArraySet.class) {
|
||||
synchronized (sBaseCacheLock) {
|
||||
if (sBaseCacheSize < CACHE_SIZE) {
|
||||
array[0] = sBaseCache;
|
||||
array[1] = hashes;
|
||||
@@ -308,10 +334,16 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
@Override
|
||||
public void clear() {
|
||||
if (mSize != 0) {
|
||||
freeArrays(mHashes, mArray, mSize);
|
||||
final int[] ohashes = mHashes;
|
||||
final Object[] oarray = mArray;
|
||||
final int osize = mSize;
|
||||
mHashes = EmptyArray.INT;
|
||||
mArray = EmptyArray.OBJECT;
|
||||
mSize = 0;
|
||||
freeArrays(ohashes, oarray, osize);
|
||||
}
|
||||
if (mSize != 0) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,6 +352,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
* items.
|
||||
*/
|
||||
public void ensureCapacity(int minimumCapacity) {
|
||||
final int oSize = mSize;
|
||||
if (mHashes.length < minimumCapacity) {
|
||||
final int[] ohashes = mHashes;
|
||||
final Object[] oarray = mArray;
|
||||
@@ -330,6 +363,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
}
|
||||
freeArrays(ohashes, oarray, mSize);
|
||||
}
|
||||
if (mSize != oSize) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -400,11 +436,10 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
*
|
||||
* @param value the object to add.
|
||||
* @return {@code true} if this set is modified, {@code false} otherwise.
|
||||
* @throws ClassCastException
|
||||
* when the class of the object is inappropriate for this set.
|
||||
*/
|
||||
@Override
|
||||
public boolean add(E value) {
|
||||
final int oSize = mSize;
|
||||
final int hash;
|
||||
int index;
|
||||
if (value == null) {
|
||||
@@ -419,9 +454,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
}
|
||||
|
||||
index = ~index;
|
||||
if (mSize >= mHashes.length) {
|
||||
final int n = mSize >= (BASE_SIZE * 2) ? (mSize + (mSize >> 1))
|
||||
: (mSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
|
||||
if (oSize >= mHashes.length) {
|
||||
final int n = oSize >= (BASE_SIZE * 2) ? (oSize + (oSize >> 1))
|
||||
: (oSize >= BASE_SIZE ? (BASE_SIZE * 2) : BASE_SIZE);
|
||||
|
||||
if (DEBUG) Log.d(TAG, "add: grow from " + mHashes.length + " to " + n);
|
||||
|
||||
@@ -429,21 +464,29 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
final Object[] oarray = mArray;
|
||||
allocArrays(n);
|
||||
|
||||
if (oSize != mSize) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
if (mHashes.length > 0) {
|
||||
if (DEBUG) Log.d(TAG, "add: copy 0-" + mSize + " to 0");
|
||||
if (DEBUG) Log.d(TAG, "add: copy 0-" + oSize + " to 0");
|
||||
System.arraycopy(ohashes, 0, mHashes, 0, ohashes.length);
|
||||
System.arraycopy(oarray, 0, mArray, 0, oarray.length);
|
||||
}
|
||||
|
||||
freeArrays(ohashes, oarray, mSize);
|
||||
freeArrays(ohashes, oarray, oSize);
|
||||
}
|
||||
|
||||
if (index < mSize) {
|
||||
if (index < oSize) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "add: move " + index + "-" + (mSize - index) + " to " + (index + 1));
|
||||
Log.d(TAG, "add: move " + index + "-" + (oSize - index) + " to " + (index + 1));
|
||||
}
|
||||
System.arraycopy(mHashes, index, mHashes, index + 1, mSize - index);
|
||||
System.arraycopy(mArray, index, mArray, index + 1, mSize - index);
|
||||
System.arraycopy(mHashes, index, mHashes, index + 1, oSize - index);
|
||||
System.arraycopy(mArray, index, mArray, index + 1, oSize - index);
|
||||
}
|
||||
|
||||
if (oSize != mSize || index >= mHashes.length) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
mHashes[index] = hash;
|
||||
@@ -458,6 +501,7 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
* @hide
|
||||
*/
|
||||
public void append(E value) {
|
||||
final int oSize = mSize;
|
||||
final int index = mSize;
|
||||
final int hash = value == null ? 0
|
||||
: (mIdentityHashCode ? System.identityHashCode(value) : value.hashCode());
|
||||
@@ -476,6 +520,11 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
add(value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (oSize != mSize) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
|
||||
mSize = index + 1;
|
||||
mHashes[index] = hash;
|
||||
mArray[index] = value;
|
||||
@@ -492,6 +541,9 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
if (N > 0) {
|
||||
System.arraycopy(array.mHashes, 0, mHashes, 0, N);
|
||||
System.arraycopy(array.mArray, 0, mArray, 0, N);
|
||||
if (0 != mSize) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
mSize = N;
|
||||
}
|
||||
} else {
|
||||
@@ -549,12 +601,14 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
// Check if exception should be thrown outside of the critical path.
|
||||
throw new ArrayIndexOutOfBoundsException(index);
|
||||
}
|
||||
final int oSize = mSize;
|
||||
final Object old = mArray[index];
|
||||
if (mSize <= 1) {
|
||||
if (oSize <= 1) {
|
||||
// Now empty.
|
||||
if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
|
||||
clear();
|
||||
} else {
|
||||
final int nSize = oSize - 1;
|
||||
if (shouldShrink()) {
|
||||
// Shrunk enough to reduce size of arrays.
|
||||
final int n = getNewShrunkenSize();
|
||||
@@ -565,31 +619,33 @@ public final class ArraySet<E> implements Collection<E>, Set<E> {
|
||||
final Object[] oarray = mArray;
|
||||
allocArrays(n);
|
||||
|
||||
mSize--;
|
||||
if (index > 0) {
|
||||
if (DEBUG) Log.d(TAG, "remove: copy from 0-" + index + " to 0");
|
||||
System.arraycopy(ohashes, 0, mHashes, 0, index);
|
||||
System.arraycopy(oarray, 0, mArray, 0, index);
|
||||
}
|
||||
if (index < mSize) {
|
||||
if (index < nSize) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "remove: copy from " + (index + 1) + "-" + mSize
|
||||
Log.d(TAG, "remove: copy from " + (index + 1) + "-" + nSize
|
||||
+ " to " + index);
|
||||
}
|
||||
System.arraycopy(ohashes, index + 1, mHashes, index, mSize - index);
|
||||
System.arraycopy(oarray, index + 1, mArray, index, mSize - index);
|
||||
System.arraycopy(ohashes, index + 1, mHashes, index, nSize - index);
|
||||
System.arraycopy(oarray, index + 1, mArray, index, nSize - index);
|
||||
}
|
||||
} else {
|
||||
mSize--;
|
||||
if (index < mSize) {
|
||||
if (index < nSize) {
|
||||
if (DEBUG) {
|
||||
Log.d(TAG, "remove: move " + (index + 1) + "-" + mSize + " to " + index);
|
||||
Log.d(TAG, "remove: move " + (index + 1) + "-" + nSize + " to " + index);
|
||||
}
|
||||
System.arraycopy(mHashes, index + 1, mHashes, index, mSize - index);
|
||||
System.arraycopy(mArray, index + 1, mArray, index, mSize - index);
|
||||
System.arraycopy(mHashes, index + 1, mHashes, index, nSize - index);
|
||||
System.arraycopy(mArray, index + 1, mArray, index, nSize - index);
|
||||
}
|
||||
mArray[mSize] = null;
|
||||
mArray[nSize] = null;
|
||||
}
|
||||
if (oSize != mSize) {
|
||||
throw new ConcurrentModificationException();
|
||||
}
|
||||
mSize = nSize;
|
||||
}
|
||||
return (E) old;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user