Java : 方法接受一个 Map 参数,需要对这个 map 做遍历。怎样才是安全的? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
theworldsong
V2EX    程序员

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

  theworldsong 2018-09-28 16:21:30 +08:00 6986 次点击
这是一个创建于 2576 天前的主题,其中的信息可能已经有所发展或是发生改变。

举例:

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

现在要在 test 内遍历 m。

但是:

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

老铁们,有办法吗

第 1 条附言    2018-09-30 09:57:43 +08:00

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

42 条回复    2018-10-07 14:05:05 +08:00
af463419014
    1
af463419014  
   2018-09-28 16:26:18 +08:00
入参 m 的类型改成 ImmutableMap
yinzhili
    2
yinzhili  
   2018-09-28 16:32:56 +08:00   2
com.google.common.collect.ImmutableMap
xuhaoyangx
    3
xuhaoyangx  
   2018-09-28 16:46:47 +08:00
你这问题 让我想起了 Java 形参和实参

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

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

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

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

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

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

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

# 如果不只是解决报错的话,那设计的接口有问题
关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2654 人在线   最高记录 6679       Select Language
创意工作者们的社区
World is powered by solitude
VERSION: 3.9.8.5 29ms UTC 12:18 PVG 20:18 LAX 05:18 JFK 08:18
Do have faith in what you're doing.
ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86