I am trying to understand "synchronized block" in Java. I have written very basic code to see what happens if I lock and change the object in thread_1 and access to it from another thread_2 (race condition) via another method. But I am in trouble to understand the behaviour because I was expecting Thread_1 would change value first and then Thread_2 would access the new value but the result was not as I expected.
public class Example {
public static void main(String[] args){
final Counter counter = new Counter();
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("THREAD_1_START");
counter.add(1);
System.out.println("THREAD_1_END");
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("THREAD_2_START");
System.out.println("GET_A_BY_THREAD_2:"+counter.getA(2));
System.out.println("THREAD_2_END");
}
});
threadA.start();
threadB.start();
}
}
public class Counter{
String A = "NONE";
public void add(long value){
synchronized (A) {
System.out.println("LOCKED_BY_"+value);
for(int i = 0; i < 1000000000; i++ ){}
setA("THREAD_"+value);
System.out.println("GET_A_BY_THREAD:"+getA(value));
}
}
public void setA(String A){
System.out.println("Counter.setA()");
this.A = A;
System.out.println("Counter.setA()_end");
}
public String getA(long value){
System.out.println("Counter.getA()_BY_"+value);
return this.A;
}
}
The Output is:
THREAD_1_START
THREAD_2_START
LOCKED_BY_1
Counter.getA()_BY_2
GET_A_BY_THREAD_2:NONE
THREAD_2_END
Counter.setA()
Counter.setA()_end
Counter.getA()_BY_1
GET_A_BY_THREAD:THREAD_1
THREAD_1_END
Thread_1 locks the "A" string object and changes it but Thread_2 can read the value before it changes. When "A" is in lock, how can thread_2 can access the "A" object?
Thread_1 locks the "A" string object and changes it
No, Thread_1 locks on the "NONE" string object, creates a new String object, and overwrites the A
field with a reference to this new object. Thread_2 can now acquire its free lock, however in your current code the getA
method doesn't even attempt to acquire it.
You must use locking for all access, not just writing. Therefore getA
must also contain a synchronized block.
The general rule is to never use mutable instance fields for locking. Doing that provides you with no useful guarantees. Therefore your A
field should be final
and you must delete all code that disagrees with that.