ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]


錯誤堆棧:

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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM