多线程环境下,同步ArrayList调用toArray()是否线程安全?需同步块吗?
关于Collections.synchronizedList中toArray()的线程安全问题
1. toArray()必须放在synchronized块中
虽然Collections.synchronizedList的单个方法(包括toArray())都是线程安全的,但你的removeAllAndProcess()是多步骤的复合操作:先拿数组、再遍历删元素、处理内容。这整套逻辑如果不锁起来,会出问题:
- 从
toArray()拿到数组到开始删除的间隙,其他线程可能已经加了新元素或者删了旧元素,导致你处理的是过时的数据; - 你遍历数组时逐个调用
myList.remove(s),要是有其他线程同时操作列表,轻则删错元素,重则出现逻辑混乱(比如重复元素只删第一个)。
正确的写法是把整个处理逻辑用synchronized (myList)包裹,而且直接用clear()清空列表比逐个删除更高效安全:
public void removeAllAndProcess() { synchronized (myList) { Object[] elements = myList.toArray(); myList.clear(); Arrays.stream(elements).forEach(item -> { process(item.toString()); }); } }
2. 并发添加会影响toArray()的返回结果
会有影响。toArray()执行过程中是线程安全的(不会返回损坏的数组),但如果其他线程在这时候调用addElements()加元素:
- 要是添加在
toArray()启动前完成,元素会被包含在返回的数组里; - 要是添加发生在
toArray()执行过程中,新元素很大概率不会出现在返回的数组中; - 简单说,
toArray()返回的是调用那一刻的列表快照,后续的并发修改不会改已经返回的数组,但快照本身可能没包含刚加的元素。
3. 原代码的其他坑点
- 用
myList.remove(s)逐个删元素效率低,而且列表里有重复元素时,remove(Object)只会删第一个匹配项,剩下的重复元素会留在列表里; process()里失败后重新加元素的逻辑本身没问题(因为addElements()调用的是synchronizedList的add方法),但如果把process()放在synchronized块内执行,能避免添加操作和当前处理流程的并发冲突,更稳妥。
内容的提问来源于stack exchange,提问作者CS1999
相关产品推荐
相关产品推荐

