3

I wrote simple multithreaded application, just to play around with concurrency but I have a problem with boolean variable which controles the loop in thread. One of the functions should stop the thread if there's noelements left in queue and I guess that is my problem because If I add something in between braces to:

while (!queue.isEmpty()) {
}
isRunning = false;

So it becomes :

while (!queue.isEmpty()) {
    System.out.println("ASD");
}
isRunning = false;

It is working a bit better - the program terminates after executing turnOff method

Any Ideas?

Here is full code of my app:

package test;

public class xxx {
    public static void main(String[] args) {
        Foo instance = Foo.getInstance();
        Thread x = new Thread(instance);
        x.start();

        for (int count = 1; count < 100000; count++)
            instance.addToQueue(count + "");
        instance.turnOff();
    }
}

And:

package test;

import java.util.LinkedList;
import java.util.List;

public class Foo implements Runnable {
    private static Foo inner = null;
    private static List<String> queue = new LinkedList<String>();
    private volatile static boolean isRunning = false;

    private Foo() { }

    public static Foo getInstance() {
        if (inner == null) {
            inner = new Foo();
        }
        return inner;
    }

    public void addToQueue(String toPrint) {
        synchronized (queue) {
            queue.add(toPrint);
        }

    }

    public void removeFromQueue(String toRemove) {
        synchronized (queue) {
            queue.remove(toRemove);
        }
    }

    public void turnOff() {
        while (!queue.isEmpty()) {
        }
        System.out.println("end");
        isRunning = false;
    }

    @Override
    public void run() {
        isRunning = true;
        while (isRunning) {
            if (!queue.isEmpty()) {
                String string = queue.get(0);
                System.out.println(string);
                removeFromQueue(string);
            }

        }
    }
}
Charles Goodwin
  • 6,402
  • 3
  • 34
  • 63
xwhyz
  • 1,444
  • 4
  • 21
  • 45
  • 3
    Do you realise what `while (!queue.isEmpty()) {}` will do? It will consume **all** your CPU resources because it will be continuously checking the state of a single flag/bit/value which will most likely be stored in CPU register. Usually you will want to add a `wait();' or similar statement in your loop – Germann Arlington Apr 02 '14 at 14:26
  • 1
    the compiler optimizes your code and kicks the empty loop out, since it doesn't do anything in his opinion – Philipp Sander Apr 02 '14 at 14:27
  • Please have a look at my post. – Braj Apr 02 '14 at 15:23
  • possible duplicate of [Loop doesn't see changed value without a print statement](http://stackoverflow.com/questions/25425130/loop-doesnt-see-changed-value-without-a-print-statement) – Boann Aug 23 '14 at 10:23
  • rather the question you've linked is a duplicate of my question since it was asked 4 days ago as of now – xwhyz Aug 25 '14 at 13:24

5 Answers5

5

It is a race condition problem. Possibly the run method (the other thread) is executed after the turnOff in in the main thread so the flag isRunning is set as true again and the loop never ends.

That would explain why with a simple System.out.println("ASD") becomes better: the isRunning=false is delayed.

IsidroGH
  • 2,037
  • 19
  • 27
3

You have lots of problems in your code.

  1. Busy loops in turnOff and wait
  2. Unsynchronized access to queue in turnOff and run
  3. Non-volatile, non-final access to inner
  4. Needlessly static isRunning and queue variables
  5. Race condition between turnOff and start invocations

Some of these are harmless in this specific instance (e.g. instance is always accessed from the main thread), but depending on your hardware configuration you are going to get bitten by some combination of the rest of them. The reason that adding the System.out "fixes" the problem is that it renders one of the busy loops less busy (fixes 1) and has an internal synchronization mechanism (fixes 2), but the others are still there.

I suggest getting rid of the isRunning variable and the test for queue.isEmpty() and replacing with a CountDownLatch.

package test;

import java.util.LinkedList;
import java.util.List; 
import java.util.concurrent.CountDownLatch;

public class Foo implements Runnable {
    private static final Foo inner = new Foo();
    private final List<String> queue = new LinkedList<String>();
    private final CountDownLatch latch = new CountDownLatch(1);

    private Foo() { }

    public static Foo getInstance() {
        return inner;
    }

    public void addToQueue(String toPrint) {
        synchronized (queue) {
            queue.add(toPrint);
        }
    }

    public void removeFromQueue(String toRemove) {
        synchronized (queue) {
            queue.remove(toRemove);
        }
    }

    public boolean isEmpty() {
        synchronized (queue) {
            return queue.isEmpty();
        }
    }

    public String getHead() {
        synchronized (queue) {
            return queue.get(0);
        }
    }

    public void turnOff() throws InterruptedException {
        latch.await();
        System.out.println("end");
    }

    @Override
    public void run() {
        while (!isEmpty()) {
            String string = getHead();
            System.out.println(string);
            removeFromQueue(string);
        }

        latch.countDown();
    }
}

And the runner

package test;

public class XXX {
    public static void main(String[] args) throws InterruptedException {
        Foo instance = Foo.getInstance();
        Thread x = new Thread(instance);

        for (int count = 1; count < 100000; count++)
            instance.addToQueue(count + "");

        x.start();
        instance.turnOff();
    }
}   
rxg
  • 3,777
  • 22
  • 42
  • I think Isidro identified the correct _specific_ reason why the code was failing, but this is a good generally instructive answer. I think it would be much easier to just use one of the concurrent list implementations though (e.g. `ConcurrentLinkedQueue` or perhaps `LinkedBlockingDeque`, I haven't checked too closely). – wds Apr 03 '14 at 07:00
1

The main problem is the race condition between adding/removing elements and checking whether the queue is empty. In more words:

Wrapping add and remove calls in synchronized block provides you guarantees that all invocations of these methods will be performed sequentially. But, there is one more access to queue variable outside of synchronized block - it is queue.isEmpty(). It means there is a chance that some thread will get the result of this call and while it performs actions inside if block, other thread may add or remove elements.

This code also has some more concurrency problems, please let me know if you want them to be discussed (they are a little bit offtopic).

Alexey Malev
  • 6,408
  • 4
  • 34
  • 52
0

As Germann Arlington point, the value of queue.isEmpty() seems to be cached in the main thread. Try synchronize it:

while (true) {
    synchronized(queue) {
        if(queue.isEmpty())
            break;
    }
} 

Or just make the queue to be volatile:

private volatile static List<String> queue = new LinkedList<String>();
davmac
  • 20,150
  • 1
  • 40
  • 68
swist
  • 1,111
  • 8
  • 18
  • It is **not** about caching the value, it is about accessing it continuously – Germann Arlington Apr 02 '14 at 15:04
  • 1
    Making the queue volatile is not the right answer (even if it seems to help). It's not the variable itself which is changing, it is the state of the object it refers to. – davmac Apr 02 '14 at 15:08
  • I admit the solution with volatile is not really correct. As it is mentioned in any posts any operation more added to this loop solves the problem. That is why I think that whole queue.isEmpty() is cached, and it is not checked later. `volatile` make the JVM to performe isEmpty operation every time. – swist Apr 02 '14 at 15:49
0

This will solve your problem.

Use volatile variable isRunning in turnOff() method's while loop also.

public void turnOff() {
    while (isRunning && !queue.isEmpty()) {
    }
    System.out.println("end");
    isRunning = false;
}
Braj
  • 46,415
  • 5
  • 60
  • 76