ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]
阿新 • • 發佈:2018-12-31
錯誤堆疊:
java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]
at android.support.v4.util.SimpleArrayMap.allocArrays(SourceFile:183)
at android.support.v4.util.SimpleArrayMap.put(SourceFile:437)
錯誤原因:
由於SimpleArrayMap 裡面使用了一個靜態變數的快取,mBaseCache,
static Object[] mBaseCache;
該變數預設有兩個資料,第1個元素是一個object[],用於存放上次的快取的mBaseCache
第二個元素是int[],用於存在hash。具體賦值程式碼可以看下面
synchronized (ArrayMap.class) { if (mBaseCacheSize < CACHE_SIZE) { array[0] = mBaseCache; array[1] = hashes; for (int i=(size<<1)-1; i>=2; i--) { array[i] = null; } mBaseCache = array; mBaseCacheSize++; if (DEBUG) Log.d(TAG, "Storing 1x cache " + array + " now have " + mBaseCacheSize + " entries"); } }
使用該陣列的地方在:
SimpleArrayMap 的allocArrays 方法裡
synchronized (ArrayMap.class) { 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; } }
下面這段程式碼是有風險的,如果mBaseCache 在多執行緒被修改了,就會把ClassCastException 異常。
mBaseCache = (Object[])array[0];
解決方法:
如果專案某個地方報這個錯誤,請把這個地方的ArrayMap替換成 HasMap. HasMap 多執行緒不會崩潰,雖然,他不是特別完好的支援。不需要把專案中所有的地方都替換掉,沒有必要。單獨執行緒,ArrayMap 完全沒有問題。
錯誤復現:這個復現起來超級麻煩,我花了一週的時間,才找到復現的漏洞,分享給大家:
/**
* 復現該問題 用了四個執行緒
* java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]
* at android.support.v4.util.SimpleArrayMap.allocArrays(SimpleArrayMap.java:157)
* at android.support.v4.util.SimpleArrayMap.put(SimpleArrayMap.java:399)
* at com.example.fragment.MainFragment$14.run(MainFragment.java:280)
* 1.首先 執行緒1 執行到put 方法的
* mArray[index<<1] = key;
* mArray[(index<<1)+1] = value;
* mSize++;
* return null;
* 最上面這個位置 目的是讓這個陣列不再是空的
*
* 2.執行執行緒2 也執行到
* mArray[index<<1] = key;
* mArray[(index<<1)+1] = value;
* mSize++;
* return null;
* 最上面這個位置 目的是讓這個put 的東西,放在第0個位置,因為put裡面會生成index,
* 讓兩個執行緒都放到index 是0 的位置
*
* 3.把執行緒1執行完,這樣資料裡面已經放進去一個數據了
*
* 4.執行執行緒3 到removeAt 方法的 freeArrays 的 mBaseCache = array; 之前
* public V removeAt(int index) {
* final Object old = mArray[(index << 1) + 1];
* if (mSize <= 1) {
* // Now empty.
* if (DEBUG) Log.d(TAG, "remove: shrink from " + mHashes.length + " to 0");
* freeArrays(mHashes, mArray, mSize);
*
* mBaseCache = array;----------- freeArrays
*
* 這個的目的是呼叫freeArray 方法,讓當前的map釋放當前的陣列。這樣就可以生成mBaseCache了
*
* 5.把執行緒2 執行完
* 這樣就會把mBaseCache 賦值的陣列,重新賦值
*
* 6.把執行緒3執行完
* ok,現在mBaseCache已經被汙染了
*
* 7.執行執行緒4
*
*/
private void CMETestCastException() {
final ArrayMap testArrayMap = new ArrayMap();
final ArrayMap testArrayMap2 = new ArrayMap();
new Thread("執行緒1"){
@Override
public void run() {
super.run();
testArrayMap.put("2324","fffff");
}
}.start();
new Thread("執行緒2"){
@Override
public void run() {
super.run();
testArrayMap.put("test","string");
}
}.start();
new Thread("執行緒3"){
@Override
public void run() {
super.run();
testArrayMap.removeAt(0);
}
}.start();
new Thread("執行緒4"){
@Override
public void run() {
super.run();
testArrayMap2.put("aaa","string");
}
}.start();
}
復現這個問題的時候,關鍵是把mBaseCache 汙染掉。這裡四個執行緒的話,需要除錯,除錯步驟就是上面我註釋的。
總結:
如果當前的map 會有多個執行緒訪問,請使用HasMap. 該問題,google 並沒有解決。在高版本上,直接扔CME ConcurrentModificationException.