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.
ArrayMap java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Object[]的更多相关文章
- java.lang.ClassCastException: oracle.sql.TIMESTAMP cannot be cast to java.sql.Timestamp
		http://stackoverflow.com/questions/13269564/java-lang-classcastexception-oracle-sql-timestamp-cannot ... 
- java.lang.ClassCastException: android.widget.RelativeLayout cannot be cast to android.widget.TextView
		最近在学习drawerLayout时,遇到这个bug.如下示: java.lang.ClassCastException: android.widget.RelativeLayout cannot b ... 
- [Ljava.lang.String; cannot be cast to java.lang.String报错的原因
		完整错误信息: java.lang.ClassCastException: [Ljava.lang.String; cannot be cast to java.lang.String 报这个错的原因 ... 
- java.lang.ClassCastException: android.app.Application cannot be cast to
		出这个异常的原因是在项目中添加了新lication类(public class Application extends lication)之后,没有在AndroidManifest.xml中添加该类的 ... 
- 关于android使用ksoap2报Caused by: java.lang.ClassCastException: org.ksoap2.SoapFault cannot be cast to org.ksoap2.serialization.SoapObject
		Caused by: java.lang.ClassCastException: org.ksoap2.SoapFault cannot be cast to org.ksoap2.serializa ... 
- java.lang.ClassCastException: oracle.sql.CLOB cannot be cast to oracle.sql.CLOB
		错误现象: [framework] 2016-05-26 11:34:53,590 -INFO [http-bio-8080-exec-7] -1231863 -com.dhcc.base.db.D ... 
- java.lang.ClassCastException: android.widget.TextView cannot be cast to android.widget.EditText
		Caused by: Java.lang.ClassCastException: Android.widget.TextView cannot be cast to android.widget.Ed ... 
- 安卓出现错误: java.lang.ClassCastException: android.widget.TextView cannot be cast to android.widget.EditText
		Caused by: Java.lang.ClassCastException: Android.widget.TextView cannot be cast to android.widget.Ed ... 
- java.lang.ClassCastException:android.widget.Button cannot be cast to android.widget.ImageView
		今天遇到一个错误也不知道怎么回事,上网搜了一下: 出现的问题是:java.lang.ClassCastException:android.widget.Button cannot be cast to ... 
随机推荐
- python接口测试-项目实践(二)获取接口响应,取值(re、json)
			一 分别请求3个接口,获取响应. 第三方接口返回有两种:1 纯字符串 2 带bom头的json字串 import requests api1 = 'url1' response1 = request ... 
- 设计模式——外观模式(FacadePattern)
			外观模式:为子系统中的一组接口提供一个一致的界面,次模式定义了一个高层接口,这个接口使得这一子系统更加容易使用. UML图: 外观类: package com.cnblog.clarck; /** * ... 
- (转)Wireshark基本介绍和学习TCP三次握手
			原地址https://www.cnblogs.com/TankXiao/archive/2012/10/10/2711777.html#filter 阅读目录 wireshark介绍 wireshar ... 
- 【转】android的编译和运行过程深入分析
			首先来看一下使用Java语言编写的Android应用程序从源码到安装包的整个过程,示意图如下,其中包含编译.链接和签名等: (1)使用aapt工具生成R.java文件 可以先通过搭建好的Eclipse ... 
- winform 实现彩票功能
			版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/MrTraum/article/details/32702319 watermark/2/text/a ... 
- 并发队列 – 有界阻塞队列 ArrayBlockingQueue 原理探究
			一.ArrayBlockingQueue类图结构 如图ArrayBlockingQueue内部有个数组items用来存放队列元素,putindex下标标示入队元素下标,takeIndex是出队下标,c ... 
- eclipse properties 文件查看和编辑插件
			*.properties属性文件,如果文件中包含中文,会出现乱码.为了解决这个问题,可以为Eclipse安装Properties Editor插件解决这个问题. 步骤 1 安装Properties ... 
- 【洛谷P2574】XOR的艺术
			XOR的艺术 题目链接 用线段树维护sum, 修改时 tag[p]^=1; sum=r-l+1-sum; 详见代码 #include<iostream> #include<cstdi ... 
- 【洛谷P3834】(模板)可持久化线段树 1(主席树)
			[模板]可持久化线段树 1(主席树) https://www.luogu.org/problemnew/show/P3834 主席树支持历史查询,空间复杂度为O(nlogn),需要动态开点 本题用一个 ... 
- Mac 使用问题
			Mac 使用 Mac改变系统截图存储位置 鼠标在屏幕中间上下滚动时,有时反应不灵敏: 看看偏好设置-> 鼠标 -> 跟踪速度或者手势部分是否有哪里设置不当: 考虑重新开启鼠标鼠标底部有一个 ... 
