1

I searched "java race condition" and saw a lot of articles, but none of them is what I am looking for.

I am trying to solve the race condition without using lock, synchronization, Thread.sleep something else. My code is here:

public class Test {

    static public int amount = 0;
    static public boolean x = false;

    public static void main(String[] args) {
        Thread a = new myThread1();
        Thread b = new myThread2();
        b.start();
        a.start();
    }

}

class myThread1 extends Thread {

    public void run() {
        for (int i = 0; i < 1000000; i++) {
            if (i % 100000 == 0) {
                System.out.println(i);
            }
        }
         while(true){
              Test.x = true;
         }
    }
}

class myThread2 extends Thread {

    public void run() {
        System.out.println("Thread 2: waiting...");
        while (!Test.x) {
        }
        System.out.println("Thread 2: finish waiting!");
    }
}

I expect the output should be:

Thread 2: waiting...
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
Thread 2: finish waiting!
(Terminated normally)

But it actually is:

Thread 2: waiting...
0
100000
200000
300000
400000
500000
600000
700000
800000
900000
(And the program won't terminate)

After I added a statement to myThread2, changed

while (!Test.x) {
}

to

while (!Test.x) {
            System.out.println(".");
}

The program terminate normally and the output is what I expected (except those ".')

I know when 2 threads execute concurrently, the CPU may arbitrarily switch to another before fetch the next instruction of machine code. I thought it will be fine if one thread read a variable while another thread write to the variable. And I really don't get it why the program will not terminate normally. I also tried to add a Thread sleep statement inside the while loop of myThread1, but the program still will not terminate.

This question puzzled me few weeks, hope any one can help me please.

Eric
  • 11
  • 2
  • 2
    Nothing guarantees you that the 2nd cpu looks outside it's local cache for the value of x: http://stackoverflow.com/questions/106591/do-you-ever-use-the-volatile-keyword-in-java (just 1 of the potential reasons) – zapl May 18 '16 at 21:50
  • 1
    ... _why_ do you want to avoid all synchronization or thread-related helpers? Note that, since you're polling in a loop, you're not actually _guaranteed_ that the loop will ever terminate anyways: the system might just keep running one thread "until it finishes". Deadlock (mostly because thread 1 never exits at all). Also, note that you're not guaranteed when that "Thread 2: waiting" message gets printed - it can happen any time before the "finished" is printed, and is certainly not synced with thread 1 in any fashion. So, what is it you're really trying to do? – Clockwork-Muse May 18 '16 at 22:09
  • 1
    Only "volatile" variables or "synchronized" blocks copy the state of members between threads. The member content is thread local otherwise. – Konrad May 18 '16 at 22:17

5 Answers5

2

Try to declare x as volatile :

   static public volatile boolean x = false;
TMG
  • 2,620
  • 1
  • 17
  • 40
  • @zapl linked a proper explaination – TMG May 18 '16 at 21:52
  • I knew it, but I don't want to use volatile or any feature related to synchronization java provided, because I want to know why it fail and what is happening when it execute. – Eric May 18 '16 at 21:54
  • You need to read the Java Language specification to know the cache and variable synchronization strategy used by the VM implementation or Java Language specification. http://stackoverflow.com/questions/17748078/simplest-and-understandable-example-of-volatile-keyword-in-java may be a description for the ''volatile'' keyxword and why it's necessary. – Konrad May 18 '16 at 22:14
2

The shared variable x is being read and written from multiple threads without any synchronisation and hence only bad things can happen.

When you have the following,

while (!Test.x) {
}

The compiler might optimise this into an infinite loop since x (the non volatile variable) is not being changed inside the while loop, and this would prevent the program from terminating.

Adding a print statement will add more visibility since it has a synchronised block protecting System.out, this will lead into crossing the memory barrier and getting a fresh copy of Test.x.

You CAN NOT synchronise shared mutable state without using synchronisation constructs.

Sleiman Jneidi
  • 22,907
  • 14
  • 56
  • 77
  • Thanks for your answer! And one more question: how java implemented their synchronisation constructs? Is that must reply on OS support? – Eric May 19 '16 at 04:42
2

Test.x isn't volatile and thus might not be synchronized between threads.

How the print-command in the second loop affects the overall behavior can't be predicted, but apparently in this case it causes x to be synchronized.

In general: if you omit all thread-related features of java, you can't produce any code, that has a well defined behavior. The minimum would be mark variables that are used by different threads as volatile and synchronize pieces of code, that my not run concurrently.

0

What you are really asking is, "Why does my program work as expected when I add a call to println()?"

Actions performed by one thread aren't generally required to be visible to other threads. The JVM is free to treat each thread as if it's operating in its own, private universe, which is often faster than trying to keep all other threads updated with events in that private universe.

If you have a need for some threads to stay up-to-date with some actions of another thread, you must "synchronize-with" those threads. If you don't, there's no guarantee threads will ever observe the actions of another.

Solving a race condition without a memory barrier is a nonsensical question. There's no answer, and no reason to look for one. Declare x to be a volatile field!

When you call System.out.println(), you are invoking a synchronized method, which, like volatile, acts as a memory barrier to synchronize with other threads. It appears to be sufficient in this case, but in general, even this is not enough to guarantee your program will work as expected. To guarantee the desired behavior, the first thread should acquire and release the same lock, System.out, after setting x to true.


Update:

Eric asks, "I am curious how volatile work, what has it done behind. I thought that everything can be created by addition, subtraction, compare, jumping, and assignment."

Volatile writes work by ensuring that values are written to a location that is accessible to all reading threads, like main memory, instead of something like a processor register or a data cache line.

Volatile reads work by ensuring that values are read from that shared location, instead of, for example, using a value cached in a register.

When Java byte codes are executed, they are translated to native instructions specific to the executing processor. The instructions necessary to make volatile work will vary, but the specification of the Java platform require that whatever the implementation, certain guarantees about visibility are met.

erickson
  • 265,237
  • 58
  • 395
  • 493
  • Sorry for my bad questioning skill. I actually want to ask "Is there any way to solve race condition without using synchronized, volatile, lock, thread.sleep, atomic function or anything make function atomic provided by Java, or OS support". I am curious how volatile work, what has it done behind. I thought that everything can be created by addition, subtraction, compare, jumping, and assignment. – Eric May 19 '16 at 05:28
  • @Eric Please see if my update gets closer to the aim of your question. – erickson May 19 '16 at 16:57
  • Yes, certainly! Thanks for your explanation about the keyword volatile. – Eric May 20 '16 at 15:41
0

Much more better would be a LOCK object you may wait in Thread2 and send a notificytion in thread 1. You are currently active waiting in Thread2 and consume a lot of CPU resources.

Dummy code example:

main() {
    Object lock = new Object();
    Thread2 t2 = new Thread2(lock);
    t2.start();
    Thread1 t1 = new Thread1(lock);
    t1.start();
    ...
}

class Thread1 {
    Object lock = null;
    public Thread1(Object lock) {
         this.lock = lock;
         ...
    }

    public void run() {
        ...
        synchronized (lock) {
             lock.notifyAll();
        }
    }
} // end class Thread1

// similar Thread2
class Thread2 {
... constructor

public void run()
{
    System.out.println("Thread 2: waiting...");
    synchronized(lock) {
         lock.wait();
    }
    System.out.println("Thread 2: finish waiting!");
}
....

This construct does not consume any CPU cycles without doing anything in "Thread2". You can create a custom number of "Thread2" instances and wait till "Thread1" is finished. You should "only" start all "Thread2" instances before "Thread1". Otherwise "Thread1" may finish before "Thread2" instances are started.

Konrad
  • 355
  • 4
  • 16