对JSONObject抛ConcurrentModificationException的一点思考

问题背景

NetUtil.get(url, arg, new NetUtil.Callback() {    @WorkerThread    @Override    public void onSucceed(JSONObject repose) {    }    @WorkerThread    @Override    public void onFailed(String msg, Throwable t) {        try {            JSONObject event = new JSONObject();            event.put("event_name", "request_error");            event.put("error_msg", String.valueOf(msg));            event.put("stacktrace", null == t ? "" : Log.getStackTraceString(t));            // 上报请求失败            statics(event);            // log显示失败详情,            // 此处抛ConcurrentModificationException            Log.e(TAG, "request product list error: " + event);        } catch (JSONException e) {            e.printStackTrace();        }    }});// 打点上报private void statics(final JSONObject event) {    ThreadUtil.bi().submit(new Runnable() {        @Override        public void run() {            // 添加公共参数            event.put("__client_ms", System.currentTimeMillis());            event.put("__client_ver", BuildConfig.VERSION_CODE);            // 上报            report(event);        }    });}

这看起来只有局部变量,好像没什么问题…

问题原因

ConcurrentModificationException是一个并发读写的问题,发生在不同线程的读写一个线程不安全的集合时。

以上代码,看似没有问题,而且也没看到有显式的线程不安全的集合。

其实不安全的集合是有的 JSONObject:在Log.e(TAG, "request product list error: " + event);中,隐藏了一个toString的语法糖: event.toString();跟进去以后是这样的:

public class JSONObject {    /**    * Encodes this object as a compact JSON string, such as:    * 
{"query":"Pizza","locations":[94043,90210]}
*/
@Override public String toString() { try { JSONStringer stringer = new JSONStringer(); writeTo(stringer); return stringer.toString(); } catch (JSONException e) { return null; } } void writeTo(JSONStringer stringer) throws JSONException { stringer.object(); for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) { stringer.key(entry.getKey()).value(entry.getValue()); } stringer.endObject(); }}

可以看到,这里有个nameValuePairs的全局变量,它的声明是:

public class JSONObject {    private final LinkedHashMap<String, Object> nameValuePairs;}

再来看,背景中的两行代码:

// 根据上面的分析,这里有个添加公共参数的写操作。statics(event);// 根据上面的分析,这里是一个读操作。Log.e(TAG, "request product list error: " + event);

到这里就清晰了,
读写不再同一个线程,又存在对象作用域拓展的问题,自然有概率发生Crash。

问题解决

// 打点上报private void statics(JSONObject event) {    // 重新创建一个对象    final eventObject =  new JSONObject(event.toString());    ThreadUtil.bi().submit(new Runnable() {        @Override        public void run() {            // 添加公共参数            eventObject.put("__client_ms", System.currentTimeMillis());            eventObject.put("__client_ver", BuildConfig.VERSION_CODE);            // 上报            report(eventObject);        }    });}

问题复盘

上面的问题,说复杂其实不复杂,一般有一些多线程相关的基本功都可以理解,但确实有些隐秘。

上报是一个很常见的案例,经过抽象抽象,代码大同小异,模型线程模型基本可如上述所示。这里想要反思的并不是埋点,亦或是解Crash,并发的问题一般不是必现问题,多半靠脑补。这里值得一提的,是迪米特法则 和 耦合程度。(可参考:https://blog.csdn.net/Fantastic_/article/details/84501992 )

很多人喜欢利用引用传参,然后内部修改属性,进而达到值返回的效果。其实仔细想想,这样拓宽了对象的作用域,延长了生命周期。不论是从软件耦合的角度,或者是设计模式的角度,其实这是有欠妥当的做法。或许笔者这么说会引来很多不屑(比如RxJava?)。

萝卜白菜各有所爱,这些古老的东西,看起来是有些过时,但不得不说,细细品味其中不乏有大智大慧。

更多相关文章

  1. Android(安卓)Sqlite数据库查询操作使用 '%?%' 的问题
  2. Android(安卓)解决多个通知发生冲突的问题
  3. Android(安卓)Studio开发过程中各种问题
  4. android解决Unknown host 'jcenter.bintray.com'问题
  5. NDK C++线程中如何调用JAVA API
  6. android post请求接口demo
  7. Android(安卓)中 ThreadLocal使用示例
  8. Android(安卓)服务(本地服务示例)(二)
  9. android dex工具打包Could not reserve enough space for object

随机推荐

  1. Mac系统Android(安卓)M源码编译并导入And
  2. Android Intent 总结
  3. 状态栏去掉机主图标
  4. Flutter Android(安卓)打包发布
  5. android post 乱码问题
  6. 【handler】Android定时每十分钟执行一次
  7. android 中对apache httpclient及httpurl
  8. Android清理设备内存具体完整演示样例(一
  9. android拍照,调用系统相册,相片上传
  10. Android api对应版本(持续更新)