When a lock holds a non-final object, can the obje

2019-02-27 08:15发布

When an object needs to be synchronized, the IDE complains if it's not set non-final (because its reference isn't persistent):

private static Object myTable;
....

synchronized(myTable){          //IDE complains!
     //access myTable here...
}

We all know the IDE complains to prevent another thread from entering the guarded block if the thread holding the lock changes the non-final object's references.

But could a synchronized object's reference also be changed by another thread B while thread A holds the lock for the same object?

2条回答
闹够了就滚
2楼-- · 2019-02-27 09:04

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.

查看更多
叛逆
3楼-- · 2019-02-27 09:08

But could a synchronized object's reference also be changed by another thread B while thread A holds the lock for the same object?

If you mean "could another thread change the value of the myTable variable, the answer is "absolutely"... assuming there's a code path that would allow that. It's a private variable, so you should be able to find all the code that can change the value.

Holding a lock just stops another thread from acquiring the same lock. It doesn't have any effect on what code can access which variables, in itself.

As a side-note, it's important to differentiate between an object, a variable, and the value of the variable (which is a reference, not an object). So there's no such thing as a "final object" - only variables (and classes and methods) can be final. Likewise there's no such thing as a "synchronized object", and you can't change an "object's reference" - you can change the value of a variable so that it's a reference to a different object. Making these distinctions clear in your mind may help you when thinking about what's going on here.

查看更多
登录 后发表回答