0

New to Java here. Just confused on the inconsistent output of this sample program. Sometimes, task_queue.size() is zero and sometimes it is not. What could be the reason for this?

Output:

t4 : [Task9999] has been executed

t1 : [Task9969] has been executed

waiting...

finished

Number of Tasks left : 4

Process finished with exit code 0

import java.util.ArrayList;

public class TaskExecutor implements Runnable {

    private ArrayList<String> arr;

    public TaskExecutor(ArrayList<String> arr) {
        this.arr = arr;
    }

    @Override
    public void run() {

            String task;
            while (arr.size() != 0 &&  (task = arr.remove(0))!=null) {
                System.out.println(Thread.currentThread().getName() + " : [" + task + "] has been executed");
            }
    }
}

import java.util.ArrayList;

public class Main {

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

        ArrayList<String> task_queue = new ArrayList<>();

        for(int i = 0; i < 10000; i++) {
            task_queue.add("Task" + i);
        }

        Thread t1 = new Thread(new TaskExecutor(task_queue));
        Thread t2 = new Thread(new TaskExecutor(task_queue));
        Thread t3 = new Thread(new TaskExecutor(task_queue));
        Thread t4 = new Thread(new TaskExecutor(task_queue));
        Thread t5 = new Thread(new TaskExecutor(task_queue));

        t1.setName("t1");
        t2.setName("t2");
        t3.setName("t3");
        t4.setName("t4");
        t5.setName("t5");

        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();

        System.out.println();

        while(t1.isAlive() || t2.isAlive() || t3.isAlive() || t4.isAlive() || t5.isAlive()) {
        }

        System.out.println("waiting...");
        Thread.currentThread().sleep(2000);
        System.out.println("finished");
        System.out.println("Number of Tasks left : " + task_queue.size());
    }

}
  • 6
    ArrayList is intentionally not thread-safe. You can either use the older class Vector, which provides thread-safety at a cost, or explicitly synchronize yourself. See also https://stackoverflow.com/questions/2444005 . – Andy Thomas Oct 04 '19 at 15:54
  • 3
    @michalk he is new to Java. at this stage it's better to understand what's going on under the hood instead of using thread-safe data structures right away – mangusta Oct 04 '19 at 16:10

2 Answers2

1

For the sake of example, one possible way of synchronizing without using synchronized collections:

public class TaskExecutor implements Runnable {

    private ArrayList<String> arr;

    public TaskExecutor(ArrayList<String> arr) {
        this.arr = arr;
    }

    @Override
    public void run() {

            String task;

                while (true) 
                {
                    synchronized(arr)
                    {
                        if(arr.size()==0)
                           break;

                        task = arr.remove(0);
                        if(task==null)
                           break;
                    }

                    System.out.println(Thread.currentThread().getName() + " : [" + task + "] has been executed");

                }//end while    

    }//end run

}//end class
mangusta
  • 3,470
  • 5
  • 24
  • 47
  • Good example. However, putting the 'work' part (ie. the System.out.println) outside the sync block might encourage better implementations from those that see your example. – Trevor Harrison Oct 04 '19 at 16:32
  • @TrevorHarrison I agree, I was thinking about moving it outside since it's not a part of critical section – mangusta Oct 04 '19 at 16:34
0

The reason for this is that ArrayList is not thread-safe.

Use one of those Collections that are thread-safe, like CopyOnWriteArrayList. Or better: Collections.synchronizedList(new ArrayList<String>());.

Some explanation of thread-safe

Kosonome
  • 470
  • 2
  • 9