【问题标题】:Java Threads with ConcurrentModificationException带有 ConcurrentModificationException 的 Java 线程
【发布时间】:2015-06-06 12:50:29
【问题描述】:


我目前正在开发我的第一个多线程软件 - 一个计算素数的程序......
基本上我创建了 n(线程数)可运行对象。这些runnables 被添加到一个ArrayList。他们检查一个数字是否是素数。如果数字是素数,我将其添加到一个长数组中以备后用。因为我希望素数在这个数组中的顺序正确,所以我需要特定的线程来等待其他线程。我通过循环遍历 ArrayList(见上文)并等待检查较低数字的线程来做到这一点。
线程完成后,我想将其从给定的 ArrayList 中删除,但我不能,因为其他线程仍在循环通过它(我猜这就是 ConcurrentModificationException 发生的原因 - 这是我第一次使用线程...... )。


我真诚地希望你们中的任何人都可以帮助我:)
非常感谢您!

马蒂亚斯

我的可运行类(我只是在main方法中创建了这个类的四个对象):

导入 java.util.ArrayList;

public class PrimeRunnable implements Runnable {

    //Static Util
    public static ArrayList<PrimeRunnable> runningThreads = new ArrayList<PrimeRunnable>();
    public static long[] primes;
    public static int nextFreeIndex = 1;
    public static long nextPossiblePrime = 3;

    //Object specific
    private long numberToCheck;
    private Thread primeThread;
    private String threadName;
    private long threadID;

    public PrimeRunnable() {
        numberToCheck = nextPossiblePrime;
        increaseNextPossiblePrime();

        threadName = "ThreadToCheck" + numberToCheck;
        threadID = numberToCheck;

        runningThreads.add(this);
    }

    @Override
    public void run() {
        boolean isPrime = true;
        double sqrtOfPossiblePrime = Math.sqrt(numberToCheck);

        long lastDevider = 0;

        for(int index = 0; index < nextFreeIndex; index++) {
            lastDevider = primes[index];
            if(numberToCheck%primes[index] == 0) {
                isPrime = false;
                break;
            }
            if(primes[index] > sqrtOfPossiblePrime) {
                break;
            }
        }

        while(lastDevider < sqrtOfPossiblePrime) {
            lastDevider += 1;

            if(numberToCheck%lastDevider == 0) {
                isPrime = false;
                break;
            }
        }

        if(isPrime) {
            //Wait for lower Threads.

            for(PrimeRunnable runnable : runningThreads) {
                if(runnable.getThreadID() < this.getThreadID()) {
                    try {
                        runnable.primeThread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            primes[nextFreeIndex] = numberToCheck;
            increaseNextFreeIndex();
            System.out.println(numberToCheck);
        }
        runningThreads.remove(this);
    }

    public void start() {
        if(primeThread == null) {
            primeThread = new Thread(this, threadName);
        }

        primeThread.start();
    }

    public void reset() {
        numberToCheck = nextPossiblePrime;
        increaseNextPossiblePrime();

        threadName = "ThreadToCheck" + numberToCheck;
        threadID = numberToCheck;

        //No need to readd into runningThread, since we only manipulate an already existing object.
        primeThread = new Thread(this, threadName);
        primeThread.start();
    }

    public static void setUpperBorder(int upperBorder) {
        if(primes == null) {
            primes = new long[upperBorder];
            primes[0] = 2;
        } else {
            System.err.println("You are not allowed to set the upper border while running.");
        }
    }

    public long getNumberToCheck() {
        return numberToCheck;
    }

    private void increaseNextPossiblePrime() {
        nextPossiblePrime += 2;
    }

    private void increaseNextFreeIndex() {
        nextFreeIndex += 2;
    }

    public long getThreadID() {
        return threadID;
    }

    public boolean isAlive() {
        return primeThread.isAlive();
    }
}

【问题讨论】:

  • 请发布堆栈跟踪。
  • 尝试使用像 Vector 这样的线程安全集合或同步 ArrayList runningThreads 和 long[] primes。
  • 您的 start() 方法看起来有些可疑。您知道Thread 只能启动一次吗?您的 PrimeRunnable.start() 方法看起来像是要多次调用它。
  • 强制任务按照它们开始的顺序完成似乎是个坏主意。您将失去很多并发处理的机会,因为您通常会有一个线程正在处理一个难题(即,一个真正为素数的数字),而没有其他已经完成任务的线程处于空闲状态,等待轮到他们返回结果。你说你需要保持结果排序。您是否考虑过将结果存储到一个容器中,以便在添加每个新成员时保持其内容排序(例如,java.util.SortedSet)?

标签: java multithreading exception arraylist concurrentmodification


【解决方案1】:

我能够使用并发列表的 Java 实现 CopyOnWriteArrayList 复制并修复该问题

这是我的主要课程

public class PrimeRunnableMain {

    public static void main(String[] args) {
        PrimeRunnable.setUpperBorder(10);
        PrimeRunnable primeRunnable1 = new PrimeRunnable();
        PrimeRunnable primeRunnable2 = new PrimeRunnable();
        PrimeRunnable primeRunnable3 = new PrimeRunnable();
        PrimeRunnable primeRunnable4 = new PrimeRunnable();
        primeRunnable1.start();
        primeRunnable2.start();
        primeRunnable3.start();
        primeRunnable4.start();
    }
}

这里是 PrimeRunnable

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

public class PrimeRunnable implements Runnable {

    // Static Util
    public static List<PrimeRunnable> runningThreads = new CopyOnWriteArrayList<PrimeRunnable>();
    public static long[] primes;
    public static int nextFreeIndex = 1;
    public static long nextPossiblePrime = 3;

    // Object specific
    private long numberToCheck;
    private Thread primeThread;
    private String threadName;
    private long threadID;

    public PrimeRunnable() {
        numberToCheck = nextPossiblePrime;
        increaseNextPossiblePrime();

        threadName = "ThreadToCheck" + numberToCheck;
        threadID = numberToCheck;

        runningThreads.add(this);
    }

    @Override
    public void run() {
        boolean isPrime = true;
        double sqrtOfPossiblePrime = Math.sqrt(numberToCheck);

        long lastDevider = 0;

        for (int index = 0; index < nextFreeIndex; index++) {
            lastDevider = primes[index];
            if (numberToCheck % primes[index] == 0) {
                isPrime = false;
                break;
            }
            if (primes[index] > sqrtOfPossiblePrime) {
                break;
            }
        }

        while (lastDevider < sqrtOfPossiblePrime) {
            lastDevider += 1;

            if (numberToCheck % lastDevider == 0) {
                isPrime = false;
                break;
            }
        }

        if (isPrime) {
            // Wait for lower Threads.

            for (PrimeRunnable runnable : runningThreads) {
                if (runnable.getThreadID() < this.getThreadID()) {
                    try {
                        runnable.primeThread.join();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            primes[nextFreeIndex] = numberToCheck;
            increaseNextFreeIndex();
            System.out.println(numberToCheck);
        }
        runningThreads.remove(this);
    }

    public void start() {
        if (primeThread == null) {
            primeThread = new Thread(this, threadName);
        }

        primeThread.start();
    }

    public void reset() {
        numberToCheck = nextPossiblePrime;
        increaseNextPossiblePrime();

        threadName = "ThreadToCheck" + numberToCheck;
        threadID = numberToCheck;

        // No need to readd into runningThread, since we only manipulate an
        // already existing object.
        primeThread = new Thread(this, threadName);
        primeThread.start();
    }

    public static void setUpperBorder(int upperBorder) {
        if (primes == null) {
            primes = new long[upperBorder];
            primes[0] = 2;
        } else {
            System.err
                    .println("You are not allowed to set the upper border while running.");
        }
    }

    public long getNumberToCheck() {
        return numberToCheck;
    }

    private void increaseNextPossiblePrime() {
        nextPossiblePrime += 2;
    }

    private void increaseNextFreeIndex() {
        nextFreeIndex += 2;
    }

    public long getThreadID() {
        return threadID;
    }

    public boolean isAlive() {
        return primeThread.isAlive();
    }
}

【讨论】:

  • 非常感谢您!我想我明白了!
  • 哇,程序现在完成了 - 但是,嗯....它比非多线程版本慢 WAAAYY... 请看我的编辑(:
【解决方案2】:

如果PrimeListener 类包含一个同步方法publishPrime,它将把素数插入到列表中的正确位置?如果从 LinkedList 的最后一个索引开始,在列表中的正确位置插入应该不会花费太多时间。

或者,您也可以将其插入SortedSet(实现:TreeSet)。我想你无论如何都不想要任何重复的素数。在这种情况下,synchronizedSortedSet 可以直接使用而不是监听器。

请注意,您似乎仍然停留在较低级别的结构上。在 Java 上并发编程时,使用更高级别的构造(执行器、期货、并发队列等)是值得的。

【讨论】:

    【解决方案3】:

    fail-fast 和 fail-safe 迭代器的主要区别是 集合是否可以在它被修改时被修改 迭代。故障安全迭代器允许这样做;快速失败的迭代器不会。

    快速失败的迭代器直接对集合本身进行操作。中 迭代,快速失败的迭代器一旦意识到 集合已被修改(即,在意识到成员已 被添加、修改或删除)并且会抛出一个 并发修改异常。一些示例包括 ArrayList、 HashSet 和 HashMap(大部分 JDK1.4 的集合都实现为 快速失败)。故障安全迭代对复制的副本进行操作 集合,因此如果集合不抛出异常 在迭代过程中被修改。示例将包括迭代器 由 ConcurrentHashMap 或 CopyOnWriteArrayList 返回。

    【讨论】:

      猜你喜欢
      • 2020-02-21
      • 2013-08-06
      • 1970-01-01
      • 1970-01-01
      • 2022-12-07
      • 1970-01-01
      • 1970-01-01
      • 2013-09-04
      • 2023-04-09
      相关资源
      最近更新 更多