Yes, it can. All non-final variables/fields can change their values/references to an object at any time, whether an object is inside or outside a guarded block. Therefore you always need to use a third party lock in combination with non-final fields:
private static Object myTable;
private final static Object LOCK;
....
synchronized(LOCK){
//access myTable here...
}
Only setting a field as final would prevent any reference changements from either inside or outside the lock. One could say that the latter is an errorful Java behaviour and references should be locked as well by calling synchronized. But that's the way it needs to be, otherwise the code would become unmaintainable.
Well, at least some new kind of field like private guardable Object o;
would be needed for that purpose. ;)
EDIT:
Here's a test case within a class Test (note that constructors load threads quite unpredictable, so therefore the wait(1000)...but that's only meant to be a test, usually you shouldn't start threads in constructors at all):
private volatile boolean first = true;
private TestObject testObject;
private Thread thread;
public Test(){
testObject = new TestObject();
thread = new Thread(this);
thread.start();
try {
synchronized(this){
wait(1000);
}
} catch (InterruptedException ex) {}
first = false;
thread = new Thread(this);
thread.start();
}
public void run() {
System.out.println("First: "+testObject.toString());
if(!first){
testObject = new TestObject();
}
synchronized(testObject){
System.out.println("Second: "+testObject.toString()+" locked!");
try {
synchronized(this){
System.out.println("Thread "+thread+" waiting!");
wait();
}
} catch (InterruptedException ex) {}
}
}
public static void main(String[] args) {
Test test = new Test();
}
The results are:
First: model.TestObject@12b6651
Second: model.TestObject@12b6651 locked!
Thread Thread[Thread-0,5,main] waiting!
First: model.TestObject@12b6651
Second: model.TestObject@4a5ab2 locked!
Thread Thread[Thread-1,5,main] waiting!
You can see on line 5 that there is no prevention of the reference changement by the lock. Then the test case was changed to:
private volatile boolean first = true;
private TestObject testObject;
private Thread thread;
private final Object LOCK = new Object();
...
public void run() {
System.out.println("First: "+testObject.toString());
if(!first){
testObject = new TestObject();
}
synchronized(LOCK){
...
Yielding this result:
First: model.TestObject@150bd4d
Second: model.TestObject@150bd4d locked!
Thread Thread[Thread-0,5,main] waiting!
First: model.TestObject@150bd4d
Here the second thread waits for acquiring the LOCK, and that's exactly what we wanted.