Search code examples
arraylistthread-safetycopyonwritearraylist

When a thread is not in (Runnable) state while active


I hope you are all doing well. So coming to the question. I have this part of a code :

private static ArrayList<String> primelist = new ArrayList<>();
static void addToList(String list_elm) {
    primelist.add(list_elm);
}

Basically this list is being accessed concurrently by multiple number of threads I have created in the full code (below), and these are responsible for making some calculations and return the result which is then going to be added to this primelist by calling upon the method addToList(String list_elm).

However, after all of the threads are put to terminated (i.e: finished their job), there exist some null values in primelist. So after some research, it turned out that ArrayList is not a thread-safe class and consequently its methods. I want to ask the following (perhaps deep) question:

Would the thread be put into (waiting // timed waiting) while its executing a line of code.., that is to say it is calling upon the method addToList(String list_elm) and it reached the line primelist.add(list_elm); but while adding the element it just happened to stop!

If not could you please clarify my confusion regarding (especially) the ArrayList case. (( basically what is going on? ^^ ))

Full code:

    import java.util.ArrayList;    
    import java.util.Iterator;

    public class CreatingAThreadThree
    {
    private static ArrayList<String> primelist = new ArrayList<>();
    static void addToList(String list_elm)
    {
        primelist.add(list_elm);
    }
    static ArrayList<String> getListReference(){
        return primelist;
    } 
    public static void main(String[] args)
    {
        for(long x = 6223372036854775899L; x<=(6223372036854775999L); x+=2)
        {
            new Thread (new MyCalcRunnable(x)).start();             
        }
        for(long x = 9223372036854774703L; x<=9223372036854774789L; x+=2 )
        {
            new MyCalcThread(x, "myChildThread"+x);             
        }

        Thread mainThread = Thread.currentThread();
        int spinner =0;
        char animation = ' ';
        System.out.println("Total number of active threads: " + Thread.activeCount());
        System.out.print("Calculating primes: ");
        while(Thread.activeCount()   >1)
        {
            spinner ++;
            switch(spinner)
            {
            case 1:
                animation = '|';
                break;
            case 2:
                animation = '/';
                break;
            case 3:
                animation = '-';
                break;
            case 4:
                animation = '\\';
                spinner = 0;
                break;
            }
            System.out.print("\b" + animation);
            try
            {
                Thread.sleep(200);
            }catch(InterruptedException ex)
            {
            }           
        }
        System.out.println("Total number of active threads: " + Thread.activeCount());
        System.out.println("Results List:");
        Iterator<?> iterator = (getListReference().iterator());
        while(iterator.hasNext())
        {
            System.out.println(iterator.next());
        }
    }
    }

    class MyCalcThread extends Thread
    {
    private long numberToFactor = 0;
    MyCalcThread(long numberToFactor, String name)
    {
        super(name);
        this.numberToFactor = numberToFactor;
        start();
    }

    @Override
    public void run()
    {
        CreatingAThreadThree.addToList(new PrimeStuff().isItPrime(this.numberToFactor));
    }
    }

    class MyCalcRunnable implements Runnable
    {
    private long numberToFactor = 0;
    MyCalcRunnable(long numberToFactor)
    {
        this.numberToFactor = numberToFactor;   
    }
    @Override
    public void run()
    {
    CreatingAThreadThree.addToList(new PrimeStuff().isItPrime(this.numberToFactor));
    }
    }

    class PrimeStuff
    {
    String isItPrime(long numberToFactor)
    {
        if(numberToFactor % 2 == 0)         
            return (numberToFactor +"is Not prime....divisible by 2");

        long squareRoot = (long)(Math.sqrt(numberToFactor));
        for(long i=3; i<squareRoot; i++)
        {
            if(numberToFactor % i == 0)
            {
                return (numberToFactor +"is Not prime....first divisible by " +                  i);    
            }
        }
        return (numberToFactor + " is Prime!!");
    }
    }

Solution

  • You are focusing on the wrong question; meaning: invest your time into fixing your broken code.

    When you have multiple threads accessing the same shared, unprotected data; all kinds of things can happen.

    Plus: just changing the type of the list you are using might not be enough. You see, that CopyOnWrite list guarantees "thread safety" for single operations. But when you have things like

    if (someList.size() > 1) {
      do something with your list
    

    is still not safe; even when using the CopyOnWrite list - because there are two calls on the list; and the list could change between the first and the second call; when some other thread changes the list in the meantime.

    Long story short: one solution is using synchronized on those of your methods that you do not want to run in parallel.

    In other words: add the protection you need; instead of confusing yourself by understanding the details of the thread status model - that part will not help you to write correct code.

    Given your comment: you try to solve this on a very "low" level. You think you have to understand threads states, and waiting conditions, and so on to come to a "good" solution. But that isn't an efficient approach; especially when you are newbie and learning these things.

    First you should worry about coming to a correct solution. And then you can go forward and enhance it; for example by doing different experiments. And in that sense, you should understand: in real multi-threading, we usually try to abstract from such low level details. Instead, we even introduce additional layers, such as Executors for example.

    What I am trying to tell you: looking into low level details is most likely not going to help you, but overburden you at this point.