美文网首页
记一次异常 JSONObject toString 出现并发修改

记一次异常 JSONObject toString 出现并发修改

作者: 古都旧城 | 来源:发表于2019-08-18 12:30 被阅读0次

    场景描述

    做数据上报组件的时候,把jsonObject对象当成了一个方法的参数,由外层传入,而操作此对象的线程可能有多个,比如:上报线程、备份线程等等,然后当数据量大的时候,偶发并发修改异常,经常发现在jsonObject.toString()的时候。
    异常

    08-13 15:10:54.207 16625-16709/com..xxx E/Tinker.UncaughtHandler: TinkerUncaughtHandler catch exception:java.util.ConcurrentModificationException
            at java.util.LinkedHashMap$LinkedHashIterator.nextEntry(LinkedHashMap.java:346)
            at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:375)
            at java.util.LinkedHashMap$EntryIterator.next(LinkedHashMap.java:373)
            at org.json.JSONObject.writeTo(JSONObject.java:719)
            at org.json.JSONStringer.value(JSONStringer.java:237)
            at org.json.JSONObject.writeTo(JSONObject.java:720)
            at org.json.JSONStringer.value(JSONStringer.java:237)
            at org.json.JSONArray.writeTo(JSONArray.java:613)
            at org.json.JSONArray.toString(JSONArray.java:585)
            at com.xxx.xxx.report.thread.CommonBackupThread.innerBackup(CommonBackupThread.java:36)
            at com.xxx.xxx.report.thread.CommonBackupThread.access$000(CommonBackupThread.java:22)
            at com.xxx.xxx.report.thread.CommonBackupThread$1.run(CommonBackupThread.java:49)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
            at java.lang.Thread.run(Thread.java:818)
    
    

    从上面可以看出,我们在调用JSONArray对象的toString()方法的时候抛出了并发修改异常。

    问题分析

    什么是并发修改异常
    这个异常一般我们还都比较熟悉,大多数场景一般出现在对集合的操作上边:一边迭代一边对集合进行修改,就很容易发生并发修改异常。

    例如下面这个实例就会出现并发修改异常

          );
            list.add(2);
            Iterator<Integer> iterator = list.iterator();
            while (iterator.hasNext()) {
                Integer s = iterator.next();
                if (s == 2) {
                    list.remove(s);
                }
    
            }
    

    具体抛出并发异常的代码分析可以参见:https://www.cnblogs.com/bsjl/p/7676209.html

    我们主要分析为何jsonObject 或者 jsonArray toString会抛出异常
    首先看jsonObject的toString 方法

        @Override public String toString() {
            try {
                JSONStringer stringer = new JSONStringer();
                writeTo(stringer);
                return stringer.toString();
            } catch (JSONException e) {
                return null;
            }
        }
    

    进入writeTo 方法,可以看到是有一个map集合nameValuePairs的遍历

       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();
        }
    

    然后在我们像jsonObject塞入数据的时候是这样的

       public JSONObject put(String name, boolean value) throws JSONException {
            nameValuePairs.put(checkName(name), value);
            return this;
        }
    

    看到这里结论就很明确了,toString有调用集合的高级for循环(底层迭代器实现的),然后添加数据的时候又会直接操作集合,读写又不在同一个线程,自然就容易出问题。

    异常发生原因回顾

    • 1、外层将上报的数据封装成了一个jsonObject 传递给了上报组件(组件内涉及分发、备份等线程)
    • 2、组件内的备份线程调用了jsonObject的toString()方法,此方法涉及集合遍历。
    • 3、于此同时外层依然在修改此对象用作其他用途,对象修改也涉及到了集合的修改。
    • 上面两个动作非同一线程串行的,而是不同线程并发操作,从而触发了集合的并发修改异常,导致崩溃。

    总结

    • 多线程操作任何对象都容易出问题,不仅仅是jsonObject,对象可能同时存在读写情况的话,不应当作为参数在多个线程之间来回传递,这样是不安全的,也是低级的错误。
    • 修改方案:这里场景是上报组件的一个参数,加锁显然是不合理的,这里我们应当保证多个场景使用的对象不是同一个(当然要具体场景具体分析,我们这里是上报数据,数据传入之后,我们无需关心外层对此对象如何更改的,所以可以直接通过创建一个新的对象避免此种情况发生)

    相关文章

      网友评论

          本文标题:记一次异常 JSONObject toString 出现并发修改

          本文链接:https://www.haomeiwen.com/subject/llqbjctx.html