天天看点

ListIterator中set(),add(),remove(),操作同步问题及源码分析

最近在学习迭代器,其中Iterator在进行迭代时,不允许对生成迭代器的Collection进行并发增加删除操作,否则会抛出线程同步异常。所以我们使用ListIterator,因为ListIterator是带有索引的迭代器,而Collection是不带索引的迭代器。所以ListIterator中有对迭代器的操作方法,修改:set(),增加:add(),删除:remove()。迭代器迭代完毕后,会将元素重新写回到list中,可以通过迭代器完成对list的增删改。

我们先new一个ArrayList()对象,将测试数据 "1"~"9" 添加进去

ListIterator中set(),add(),remove(),操作同步问题及源码分析

再定义一个ListIterator迭代器,并且先输出list

ListIterator中set(),add(),remove(),操作同步问题及源码分析

然后遍历i,并且在匹配到4的时候,先重新赋值,再移除这个下标时的值,再添加一个"add"

ListIterator中set(),add(),remove(),操作同步问题及源码分析

输出如下:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

第一栏为原始list,第二栏为set修改值后的list,第三栏为remove()和add()操作后的list

然而我们改变一下执行顺序,先remove(),再set

ListIterator中set(),add(),remove(),操作同步问题及源码分析

则会报错IllegalStateException:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

通过API,我们发现,这是一个。具体自行百度下。这里主要结合源码讲解一下。

ListIterator中set(),add(),remove(),操作同步问题及源码分析

首先用eclipse打开源码,因为ArrayList是ListIterator接口的实现类,所以我们在ArrayList中寻找,笔者用debug找到了

ListIterator中set(),add(),remove(),操作同步问题及源码分析

这里返回一个Itr的对象,这个Itr中,定义了三个属性,其中我们要关注的cursor,lastRet

如注释所见,cursor是返回当前的元素下标,lastRet是上一个元素的下标值,默认为-1,即没有上一个元素

ListIterator中set(),add(),remove(),操作同步问题及源码分析

然后我们看一下报错的set()方法:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

这里lastRet < 0,所以抛出异常,为什么要抛出这个异常呢,很简单,如果一个迭代器还未开始迭代,此刻指针没有指向一个元素,此时cursor没有值,lastRet是默认的-1,既然没有指向的元素,所以不能set值,此道理等同于remove()方法,因为remove()方法也要求此时指针指向一个元素才能remove()。

接着我们看一下remove()方法:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

果然,首先判断了lastRet是否小于0,即此刻有没有指向一个元素。

并且,执行完remove()方法后,会将lastRet的值设为-1,所以remove()完再set()会抛异常。

那么为什么按照第一次先set(),再remove(),最后再add()就能顺利通过呢?

接下来我们看add()的源码:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

可以看到,add()方法并没有对lastRet下标的校验,即可以在首元素前就进行添加。并且添加后,又将lastRet的值设为-1

这里肯定又有疑惑了,既然add()后将lastRet的值设为-1,为什么while循环还能继续呢?我们接着来看next()方法:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

可以看到,虽然add()后lastRet = -1,但是执行next()方法后,会重新给lastRet = i 赋值为 i,而将cursor = i + 1,所以执行next()方法后,cursor指向下一个元素下标,而lastRet仍为上一个元素下标。

既然set()方法和remove()方法会校验lastRet下标的值,即上一个元素的下标,是否是因为执行了set()和remove()操作让上一个元素的下标产生了变化呢?这里我们用previousIndex()方法来进行验证,lastRet = cursor -1,该方法返回的值即可认为是lastRet

ListIterator中set(),add(),remove(),操作同步问题及源码分析

验证代码如下:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

执行完结果如下:

ListIterator中set(),add(),remove(),操作同步问题及源码分析

可以看到,执行set()方法不会改变lastRet的值,而执行remove()后,lastRet的值从3变为2,执行add()后,lastRet的值从2变成3.为什么会这样设计呢?因为执行add,remove操作后,当前index的值就改变了,上一个下标值也改变了,并且之后元素所有下标的值都会发生改变,所以再进行set(),remove() 操作可能会误修改和误删,所以add,remove操作后,直接将lastRet值设为-1,就是提示迭代器状态已经发生改变,进行set()和remove() 操作会不准确。因为ArrayList线程是不安全的,所以在线程对ArrayList进行add()和remove()操作后,将lastRet值设为-1可以避免另外一个线程对ArrayList的操作出错。

如果有其他更好解答,请留言解答,不胜感激!