I am new to Java multithreading. I am learning the concept of race condition.
Based on the Oracle document
http://docs.oracle.com/javase/tutorial/essential/
The simplest kind of race condition is where two threads are updating some shared data using this pattern
read a value
think for a bit, giving another thread a chance to get in
increment the value and write it back
So now if you have two threads running, each incrementing a counter whose initial value is 43, we expect this
A reads value 43
A thinks
A increments and writes 44
B reads value 44
B thinks
B increments and writes 45
but this could happen, because of the "think window"
A reads value 43
A thinks
B reads value (it's still) 43
B thinks
B increments 43 to 44 and writes
A increments 43 to 44 and write
// the value is now 44, and we expected it to be 45
The key idea for a race is that you get unexpectedly bad effects, for example in an inventory application, two threads each decrement the amount of stock, and just like in the example above we "lose" one of the decrements.
Now your code has two issues:
1). no shared values so we have no chance to see any such contention
2). You're incrementing an integer in a single line of code, so there's very little chance of two threads clashing. In simulating a race it's better to separate the read and write as I show above, and then create a "window of opportunity" by sleeping to simulate thinking time. In multi-processor environment where threads may truly be running in parallel even a single line of code could conceivably get a race because the JVM will internally be doing reads and writes and may even keep a cache of the values.
If the prints are shown "correctly", is because there are not much threads. Try to create 100 threads and you will see that prints are not shown sorted. If c
in SynchronizedCounter is static, you can see race condition, since the threads are reading the same variable.
In order to have a race between two threads, there must be shared state between those two threads and interaction (reading and writing) to that state must occur outside of a mutualy exclusive block (aka syncronized). Reading, incrementing and then writing back to a volatile field outside of a synchronized block is a great example of this.
For example, consider this situation documented on this blog.
Both thread A and B could read the counter before any modification occurs. They then both increment, and they then both write. The end result will then be 18, and not 19. For it to have been 19, we would have needed thread B to read the counter AFTER thread A had written to the counter. Which, can happen sometimes. That is why it is called a race.
To reliably achieve this kind of race, change your test code above to create the counter outside of the threads and then pass it in to them via their constructors.
The second problem that you have is that the window for the operations to overlap is very fine, and given that starting a thread has, in comparison a lot of over head then the chances of these three threads over lapping at just the right time is very low. Thus to increase their odds, you should repeat the runs in a tight loop.
The following code demonstrates the two concepts above. The changes made have been:
.
public class CounterTest {
public static void main(String[] args) throws InterruptedException {
MyCounter counter = new MyCounter();
Thread thread1 = new Thread(new CounterIncRunnable(counter));
thread1.setName("add thread");
thread1.start();
Thread thread2 = new Thread(new CounterIncRunnable(counter));
thread2.setName("add thread2");
thread2.start();
thread1.join();
thread2.join();
System.out.println(counter.value());
}
}
class CounterIncRunnable implements Runnable {
private MyCounter counter;
public CounterIncRunnable(MyCounter counter) {
this.counter = counter;
}
public void run() {
for ( int i=0; i<1000000; i++ ) {
counter.increment();
}
}
}
class MyCounter {
private volatile int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
Finally, just for fun; add synchronized to the increment method of MyCounter and then rerun. The race condition will disappear, and now the program will correctly print 2000000. This is because every call to increment will now only allow one thread in to the shared method at a time. Thus serializing each access to the shared variable c, and putting an end to the race.
You are operating on a different object in each thread, thus there is no race condition.So first you need to share the SynchronizedCounter
(btw this is a confusing name). Add a counter
member in each runnable.
CounterIncThread(SynchronizedCounter counter)
{
this->counter = counter;
}
CounterDecThread(SynchronizedCounter counter)
{
this->counter = counter;
}
...
SynchronizedCounter counter = new SynchronizedCounter();
Thread thread1 = new Thread(new CounterIncThread(counter));
Thread thread2 = new Thread(new CounterDecThread(counter));
Thread thread3 = new Thread(new CounterIncThread(counter));
Also. You are doing only one operation in the runnable. This may not be enough to display the race condition. So loop over a great amount of time.
for(int i = 0; i < 100000; i++) <-- 100000 is just on the top of my head
{
counter.increment();
}
The value will not be the sum of the operation if the race occured,in my case I expect it to be 100000 * 2
.
To be even more explicit, run several time. You will likely obtain different values