Search code examples
javamultithreadingexceptionarraylistconcurrentmodification

Java Threads with ConcurrentModificationException



I'm currently working on my first multithreaded software - a program, which calculates prime numbers...
Basically I create n (number of Threads) runnables. These runnables are added to an ArrayList. They check, whether a number is a prime. If the number is a prime I add it into an long array for later use. Since I want the primes to be in correct order in this array I need specific Threads to wait for others. I do this by looping through the ArrayList (see above) and wait for the threads, which check a lower number.
After a thread is done I want to remove it from the given ArrayList, but I cant since the other threads are still looping through it (This is the reason why the ConcurrentModificationException occurs I guess - This is my first time working with threads...).


I honestly hope that any of you guys can help me :)
Thank your really much!

Matthias

My runnable class (I just create four objects of this class in the main method):

import 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();
    }
}

Solution

  • I was able to replicate the issue and fix it using Java implementation of a concurrent list CopyOnWriteArrayList

    Here's my main class

    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();
        }
    }
    

    and here's 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();
        }
    }