• 请不要在回答技术问题时复制粘贴 AI 生成的内容
theworldsong
V2EX  ›  程序员

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

  •  1
     
  •   theworldsong · Sep 28, 2018 · 8002 views
    This topic created in 2959 days ago, the information mentioned may be changed or developed.

    举例:

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

    现在要在 test 内遍历 m。

    但是:

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

    老铁们,有办法吗

    Supplement 1  ·  Sep 30, 2018

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

    42 replies  •  2018-10-07 14:05:05 +08:00
    af463419014
    af463419014
    Sep 28, 2018
    1
    入参 m 的类型改成 ImmutableMap
    yinzhili
    yinzhili
    Sep 28, 2018❤️2
    2
    com.google.common.collect.ImmutableMap
    xuhaoyangx
    xuhaoyangx
    Sep 28, 2018
    3
    你这问题 让我想起了 Java 形参和实参

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

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

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

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

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

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

    解释一下:只有多线程共享、并且线程不安全的 Map 对象,才有可能出现这边正在处理着,那边 put/remove 的情况。
    luozic
    luozic
    Sep 29, 2018 via iPhone
    40
    这种多线程请求还不同步的,脑袋被踢了? 暂时可以抽象为,只有一个线程有取数据和移除数据的操作,其他线程都从提交到这个线程处理。
    troywinter
    troywinter
    Sep 30, 2018
    41
    我记得没错的话,你对 java 传参有什么误解,实现上来说,java will make a copy of this parameter,加 final 修饰并不能解决问题,final 只能禁止重新赋值,不能禁止修改这个对象。
    otmoc
    otmoc
    Oct 7, 2018
    42
    # 如果只是解决报错的问题的话
    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
    }

    # 如果不只是解决报错的话,那设计的接口有问题
    About   ·   Help   ·   Advertise   ·   Blog   ·   API   ·   FAQ   ·   Privacy   ·   Solana   ·   2968 Online   Highest 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 83ms · UTC 11:35 · PVG 19:35 · LAX 03:35 · JFK 06:35
    ♥ Do have faith in what you're doing.