• 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.

  • 相关阅读:
    centos7安装php7
    将centos7镜像源更新为阿里镜像源
    CentOS7 vscode连接本地虚拟机vsftp服务器
    php 查看扩展,配置文件路径命令
    centos查看程序监听的端口
    centos7搭建ftp服务
    redis-事务
    kettle 执行 kjb 临时文件夹 /tmp permission denied 问题
    Spring 声明式事务与编程式事务详解
    进程和线程的区别
  • 原文地址:https://www.cnblogs.com/caoxinyu/p/10568529.html
Copyright © 2020-2023  润新知