首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么在运行列表时通过迭代器删除项时会引发异常?

为什么在运行列表时通过迭代器删除项时会引发异常?
EN

Stack Overflow用户
提问于 2020-11-25 16:41:55
回答 1查看 500关注 0票数 0

我调用了类ListIterator的remove方法的代码,并且我不明白为什么在运行列表时删除一个项之后,在尝试获取删除后的下一个元素时会抛出一个异常。以下是我所读到的资料来源:

代码语言:javascript
复制
   public void remove() {

                if (lastRet < 0)


                    throw new IllegalStateException();


                checkForComodification();

                try {
                    SubList.this.remove(lastRet);

                    cursor = lastRet;
                    lastRet = -1;
                    expectedModCount = ArrayList.this.modCount;


                } catch (IndexOutOfBoundsException ex) {


                    throw new ConcurrentModificationException();
                }
            }

我不明白的是:

  1. 为什么remove方法获取lastRet而不是游标。我们希望删除当前项,而不是我们已经传递的项?

2.为什么lastRet设置为-1?

3.下面的行是: expectedModCount = ArrayList.this.modCount;set *expectedModCount *等于modCount,因此在下一次迭代中,当两个变量都被检查是否相等时,if将“表示”true和everything ok。我在网上读了很多文章,其中有一些答案,但还是无法理解。

由于我得到的响应表明代码不会导致运行时异常,下面是导致此异常的代码:

代码语言:javascript
复制
    public class Testing
{
    public static void main(String[] args)
    {
        List <String> list = new ArrayList<String>();
        list.add("a");
        list.add("b");
        list.add("c");
        list.add("d");
        list.add("e");

        Iterator<String> iterator = list.listIterator();
        while (iterator.hasNext())
        {
            String st = iterator.next();
            if (st.equals("c"))
            {
                list.remove(index);
            }

            else
            {
                System.out.println(st);
            }
        }
}
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2020-11-25 17:51:34

  1. Iterator.remove()的javadoc表示:

代码语言:javascript
复制
Removes from the underlying collection the last element returned by this iterator

因此,游标在调用该方法时指向下一个元素,因此使用了lastRet。

  1. -1意味着找不到。它被设置为-1,因为您刚刚删除了lastRet,因此它不再存在。这意味着如果不首先调用next(),就不能再次调用remove():如果查看next(),它将更新lastRet

  1. 调用ArrayList.this.remove()更改modCount (modCount由AbstractList管理,这是您正在查看的ArrayList迭代器的一个超类)。由于此更改是由方法本身引起的,因此我们知道这是一个有效的更改(换句话说,我们只是请求更改列表)。因此,局部变量(expectedModCount)被更新。如果这个列表是由迭代器的另一个实例修改的,那么抽象list的modCount会改变,但是属于当前实例的expectedModCount不会改变。

额外问题:

我明白。就问个小问题。如果我有两个不同的线程在同一个列表上使用for/while循环运行(而不是使用Iterator)。其中之一删除一个项目,在这种情况下不会出现异常?只有当使用迭代器系统时才是“安全”的?

多线程不是一个“小”问题:)

简单的回答是,不,这两种情况下都不安全。无论您使用哪一个循环,由于一个线程正在检查执行另一个循环是否安全(是.hasNext()还是i< size()),另一个线程可以同时删除/添加元素。因此,如果使用for循环,则不会获得并发修改异常,但可能会得到超出范围的索引异常。

你应该自己试试。下面是一些令人讨厌的示例(我使用LinkedList进行快速删除操作,因为使用迭代器从列表的开头删除对象非常缓慢)

Iterator (抛出ConcurrentModificationException):

代码语言:javascript
复制
import java.util.*;
import java.util.concurrent.*;

public class Main {

    public static void main(String[] args) throws ExecutionException, InterruptedException {

        List<Long> list = new LinkedList<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        Iterator<Long> iterator = list.iterator();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                while (iterator.hasNext()) {
                    iterator.next();
                    iterator.remove();
                    count++;
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();
    }

}

替换为for i循环(引发IndexOutOfBoundsException):

代码语言:javascript
复制
        List<Long> list = new ArrayList<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                for (int j = 0; j < list.size(); j++) {
                    list.remove(list.size() - 1);
                    count++;
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();

要避免这些异常,可以做的一件事是使用synchronized块,确保每次只允许在内部执行一个线程--在输入同步块后,有必要再次检查条件是否仍然为真:

代码语言:javascript
复制
        List<Long> list = new LinkedList<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        Iterator<Long> iterator = list.iterator();
        Object lock = new Object();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                while (iterator.hasNext()) {
                    synchronized (lock) {
                        if (iterator.hasNext()) {
                            iterator.next();
                            iterator.remove();
                            count++;
                        }
                    }
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();

Java还有并发集合,您可以使用这些集合来启用多个线程进行并行计算。

为每个线程提供自己的迭代器,需要更改为类似ConcurrentLinkedQueue的内容,以避免并发修改异常。但是,代码给出了一个非常错误的结果:

代码语言:javascript
复制
        Collection<Long> list = new ConcurrentLinkedQueue<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        Object lock = new Object();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                Iterator<Long> iterator = list.iterator();
                while (iterator.hasNext()) {
                    synchronized (lock) {
                        if (iterator.hasNext()) {
                            iterator.next();
                            iterator.remove();
                            count++;
                        }
                    }
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();

打印出来

代码语言:javascript
复制
sum of removed items = 1105846

因此,迭代器似乎不是非常线程安全的:)

如果我们去掉迭代器:

代码语言:javascript
复制
        Queue<Long> list = new ConcurrentLinkedQueue<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                while (!list.isEmpty()) {
                    list.poll();
                    count++;
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();

这样做更好:

代码语言:javascript
复制
sum of removed items = 1000007

把锁放回去:

代码语言:javascript
复制
        Queue<Long> list = new ConcurrentLinkedQueue<>();
        for (long i1 = 0; i1 < 1_000_000; i1++) {
            list.add(i1);
        }

        ExecutorService executorService = Executors.newFixedThreadPool(10);
        List<Callable<Integer>> tasks = new ArrayList<>();
        Object lock = new Object();
        for (int i = 0; i < 10; i++) {
            tasks.add(() -> {
                int count = 0;
                while (!list.isEmpty()) {
                    synchronized (lock) {
                        if (!list.isEmpty()) {
                            list.poll();
                            count++;
                        }
                    }
                }
                System.out.println("Thread " + Thread.currentThread() + " removed " + count + " items");
                return count;
            });
        }
        List<Future<Integer>> results = executorService.invokeAll(tasks);
        int sum = 0;
        for (Future<Integer> result : results) {
            sum += result.get();
        }
        System.out.println("sum of removed items = " + sum);
        executorService.shutdown();

似乎很管用

代码语言:javascript
复制
sum of removed items = 1000000
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/65009058

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档