What is the output of the following script:
public class test {
public static int globalVar;
public static void main(String[] args) {
globalVar = 1;
MyParallelClass a = new MyParallelClass();
MyParallelClass b = new MyParallelClass();
new Thread(a).start();
new Thread(b).start();
System.out.println(globalVar);
}
}
public class MyParallelClass implements java.lang.Runnable {
public int counter = 0;
@Override
public void run() {
if (test.globalVar > 0) {
for (int i = 0; i < 1000000; i++) {
counter++;
}
test.globalVar--;
}
}
}
. . . . . . . . . . . . . . . . .
Answer
0
, 1
or -1
.
Explanaition
First the simple ones:
0
is the result you would expect. One thread executes and reduces globalVar
to 0
, the other one does nothing and then globalVar
gets printed.
If the main program is faster than any of the two threads, it prints 1
before globalVar
gets reduced.
Now the most interesting one: -1
. This is called a race condition. You have to know that globalVar--
is not an atomic operation. First you have to get the value, then you have to reduce it and after that you can save the value.
This is an order of execution which would lead to a wrong value:
First Thread | Second Thread | test.globalVar |
---|---|---|
```text checks if (globalVar > 0) looping ... looping ... execute test.globalVar--; ``` | ```text . checks if (globalVar > 0) execute all four bytecode commands of "test.globalVar--;" . ``` | ```text 1 1 0 -1 ``` |
Resolve this problem
- Use join() if you don't want to get
1
as output. - If you don't want to get
-1
, you should take a look at the keyword synchronised.
A side note
With javap -c MyParallelClass
you can view the bytecode of the class MyParallelClass
. It looks like this:
Compiled from "MyParallelClass.java"
public class MyParallelClass extends java.lang.Object
implements java.lang.Runnable{
public int counter;
public MyParallelClass();
Code:
0: aload_0
1: invokespecial #1; //Method java/lang/Object."<init>":()V
4: aload_0
5: iconst_0
6: putfield #2; //Field counter:I
9: return
public void run();
Code:
0: getstatic #3; //Field test.globalVar:I
3: ifle 38
6: iconst_0
7: istore_1
8: iload_1
9: ldc #4; //int 1000000
11: if_icmpge 30
14: aload_0
15: dup
16: getfield #2; //Field counter:I
19: iconst_1
20: iadd
21: putfield #2; //Field counter:I
24: iinc 1, 1
27: goto 8
30: getstatic #3; //Field test.globalVar:I
33: iconst_1
34: isub
35: putstatic #3; //Field test.globalVar:I
38: return
}
Some links to the reference: getstatic, iconst_1, isub, putstatic
The interesting part of the bytecode is:
30: getstatic #3; //Field test.globalVar:I
33: iconst_1
34: isub
35: putstatic #3; //Field test.globalVar:I
You can see that the JVM has to execute 4 commands for test.globalVar--;
.