首页   注册   登录
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX  ›  程序员

Java : 方法接受一个 Map 参数,需要对这个 map 做遍历。怎样才是安全的?

  •  1
     
  •   theworldsong · 356 天前 · 4385 次点击
    这是一个创建于 356 天前的主题,其中的信息可能已经有所发展或是发生改变。

    举例:

    test(Map<String, String> m) {
    
    }
    

    现在要在 test 内遍历 m。

    但是:

    • m 是外部传入的参数,外部随时可能异步 remove 元素,导致你遍历出错。
    • 你不能修改外部业务,因为极为复杂。因此你只能在 test 方法内发挥。
    • 拷贝?拷贝的过程用到遍历,同样会出错。
    • 使用 SynchronizedMap ? SynchronizedMap 使用的仍然是这个 m 的引用,外部不受限制。

    老铁们,有办法吗

    第 1 条附言  ·  354 天前

    注意审题:你不能修改外部代码。包括入参的类型,你也不能修改。你只能在 test 内发挥。

    42 回复  |  直到 2018-10-07 14:05:05 +08:00
        1
    af463419014   356 天前
    入参 m 的类型改成 ImmutableMap
        2
    yinzhili   356 天前   ♥ 2
    com.google.common.collect.ImmutableMap
        3
    xuhaoyangx   356 天前
    你这问题 让我想起了 Java 形参和实参

    删除操作用 Iterator 去做
        4
    z3jjlzt   356 天前
    final 修饰入参
        5
    johnj   356 天前
    Collections.unmodifiableMap() 包一下
        6
    johnj   356 天前
    我说的不对
        7
    hand515   356 天前
    @z3jjlzt #4 这个不对,参数的 final 搞错了
        8
    linlinismine   356 天前
    copy 一份
        9
    psuwgipgf   356 天前
    不明白怎么解决,关注一下。
        10
    aa6563679   356 天前 via iPhone
    @linlinismine 可能 copy 中被改了
        11
    crayygy   355 天前 via Android
    8l 的方法是可以的,在传入之前先从原 collection 中复制一份,然后再传入复制出来的对象
        12
    elgae   355 天前
    加个锁,遍历时独占
        13
    sagaxu   355 天前 via Android   ♥ 1
    Map 只是个接口,你需要一个支持并发读写的实现
        14
    talen666   355 天前
    把声明 Map 的地方改成线程安全的 Map
        15
    psuwgipgf   355 天前
    @talen666 方法可以,不过他说的业务复杂用的地方也多,这样会影响整个系统的速度吧??
        16
    oaix   355 天前
    重试
        17
    boywang004   355 天前
    只要调用 iterator()都会有机会上抛 ConcurrentModifyException,先做保护性拷贝,拷贝期间肯定会调用 iterator(),只要捕获 CME 重新拷贝,直到某次拷贝时没有改动成功拷出来为止……

    上面说的加锁包 unmodifiableMap 啥的……=__=b
        18
    micean   355 天前
    只能在 test 里面操作的话根本做不到
        19
    ysweics   355 天前
    明确一下问题,具体的需求是怎么样,比如这个 map 开始传递过来的时候只有 10 个 kv ,然后你在便利的时候,remove 两个 kv,你需要的结果是开始的 10 个 kv,还是剩余的 8 个 kv
        20
    zzorzz   355 天前   ♥ 1
    只是在 test 遍历,没有新启动一个新线程进行遍历的话,应该无须考虑同步问题,谁调用谁负责(方法调用者在自己的线程去考虑和别的线程同步)
        21
    reeco   355 天前
    外部 remove 了跟你内部怎么遍历没有冲突啊,至于数据同步的问题就是楼上说的谁调用谁负责
        22
    miao1007   355 天前 via Android
    最开始应该上写时复制
        23
    lovedebug   355 天前
    所有调用 test 函数的地方强制使用 Collections.unmodifiedXX
        24
    Ginsai   355 天前
    既然只是传入 Map,外部能随时修改的话对 test()方法操作本身就已经不安全了。。如果没法将传入 Map 的类型线程安全的,那么就在 test()里面做 try catch,设置遍历失败尝试次数,异常捕获之后继续遍历处理。
        25
    laxenade   355 天前
    就和 spinlock 的原理一样不断拷贝咯,直到没有异常为止。
        26
    fengdianxun   355 天前 via Android
    用 kotlin 的只读 map 呢
        27
    hearfish   355 天前
    并发的环境下多线程共用一个不知道是不是线程安全的 Map,而且还不知道别的线程是怎么用它的?
        28
    MoHen9   355 天前 via Android
    把 map 改为 ConcurrentHashMap 呢,如果不允许,那么在传过来的时候就应该是一个拷贝的 map,而不是原始的 map
        29
    mifly   355 天前 via Android
    这样的代码应该要避免,考虑看看是不是可以用 blockquene 替代 map
        30
    assiadamo   355 天前 via Android
    上面的 boywang 貌似对不可变集合有不同的观点,我为了避免遍历中对原始集合有增改的这个问题,一般都不会去操作原始集合,而是直接做一个新的集合然后赋值,直接切换引用,这个是绝对线程安全的,而且遍历时切换也对遍历毫无影响
        31
    D3EP   355 天前 via iPhone
    @assiadamo 你这说的不就是 copyonwrite 的集合么?都是现成的线程安全的集合
        32
    D3EP   355 天前 via iPhone
    多线程条件下,不用线程安全的集合,我也是醉了
        33
    ilaipi   355 天前
    感觉你这既然外部可以随时修改这个 map,是不是这个 map 是个常量?那是不是不需要传进来?统一的地方去维护这个 map 然后上锁?
        34
    assiadamo   355 天前 via Android
    @D3EP copyonwrite 帮你省掉了切换引用这步操作,让你可以直接 add remove,但需要原始集合声明为 copyonwrite
        35
    TommyLemon   355 天前
    序列化再反序列化就行了。
    可以用 fastjson( https://github.com/topics/fastjson)
        36
    ColinWang   355 天前
    Copy-On-Write 正解
        37
    ZSeptember   355 天前
    设计有问题,在多个地方都会写的不应该传参。
    改用共享的,然后用线程安全的。
        38
    lihongjie0209   355 天前   ♥ 1
    你这个接口有问题, 一个接口连自己的参数都无法处于一个确定的状态, 那你怎么写业务逻辑?
        39
    passerbytiny   355 天前
    引用以下前面的回复

    #19 只是在 test 遍历,没有新启动一个新线程进行遍历的话,应该无须考虑同步问题,谁调用谁负责(方法调用者在自己的线程去考虑和别的线程同步)

    # 37 你这个接口有问题, 一个接口连自己的参数都无法处于一个确定的状态, 那你怎么写业务逻辑?

    如果你这个方法的参数类型是 java.util.Map ,又没有对外部调用做任何附加规范限制,那就意味着该方法声明了它处理的是不安全的 Map,出错是正常的,需要外部系统确认自己处理 Map 的线程安全问题。如果你需要这个方法自己考虑同步问题,那么它至少需要在规范上限制传入的参数类型是线程同步的或者不可变的。

    从你的描述看,调用方非常明确的告诉你传入的是一个没有线程同步还被多线程共享的 java.util.Map 对象。你赶紧把这个 BUG 提交上去吧,技术层面解决不了。

    解释一下:只有多线程共享、并且线程不安全的 Map 对象,才有可能出现这边正在处理着,那边 put/remove 的情况。
        40
    luozic   354 天前 via iPhone
    这种多线程请求还不同步的,脑袋被踢了? 暂时可以抽象为,只有一个线程有取数据和移除数据的操作,其他线程都从提交到这个线程处理。
        41
    troywinter   354 天前
    我记得没错的话,你对 java 传参有什么误解,实现上来说,java will make a copy of this parameter,加 final 修饰并不能解决问题,final 只能禁止重新赋值,不能禁止修改这个对象。
        42
    otmoc   347 天前
    # 如果只是解决报错的问题的话
    String[] keys = map.keySet().toArray(new String[map.size()]);
    for (String key : keys) {
    String value = map.get(key);
    if (value == null) {
    // do something
    }
    //do others something
    }

    # 如果不只是解决报错的话,那设计的接口有问题
    关于   ·   FAQ   ·   API   ·   我们的愿景   ·   广告投放   ·   感谢   ·   实用小工具   ·   2998 人在线   最高记录 5043   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.3 · 23ms · UTC 10:43 · PVG 18:43 · LAX 03:43 · JFK 06:43
    ♥ Do have faith in what you're doing.