Когда блокировка содержит объект, не являющийся конечным, может ли ссылка на объект по-прежнему изменяться другим потоком?

Когда объект должен быть синхронизирован, среда IDE жалуется, если она не установлена ​​не окончательной (поскольку ее ссылка не является постоянной):

private static Object myTable;
....

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

Мы все знаем, что жалобы IDE жалуются на то, чтобы предотвратить включение другого потока в защищенный блок, если поток, содержащий блокировку, изменяет ссылки на неконфиденциальные объекты.

Но может ли ссылка на синхронизированный объект также быть изменена другим потоком B, в то время как поток A удерживает блокировку для одного и того же объекта?

Ответ 1

Но может ли ссылка на синхронизированный объект также быть изменена другим потоком B, в то время как поток A удерживает блокировку для одного и того же объекта?

Если вы имеете в виду "если другой поток изменит значение переменной myTable, ответ будет" абсолютно "... если у вас есть путь к коду, который позволит это. Это частная переменная, поэтому вы должны иметь возможность найти весь код, который может изменить значение.

Удержание блокировки просто останавливает другой поток от получения той же блокировки. Это не влияет на то, какой код может получить доступ к этим переменным сам по себе.

В качестве примечания важно различать объект, переменную и значение переменной (которая является ссылкой, а не объектом). Таким образом, нет такой вещи, как "конечный объект" - только переменные (и классы и методы) могут быть окончательными. Точно так же нет такой вещи, как "синхронизированный объект", и вы не можете изменить "ссылку на объект" - вы можете изменить значение переменной, чтобы она ссылалась на другой объект. Устранение этих различий в вашем уме может помочь вам, если подумать о том, что происходит здесь.

Ответ 2

Да, это возможно. Все не конечные переменные/поля могут изменять свои значения/ссылки на объект в любое время, независимо от того, находится ли объект внутри или вне защищенного блока. Поэтому вам всегда нужно использовать сторонний замок в сочетании с нефинальными полями:

private static Object myTable;
private final static Object LOCK;
....

synchronized(LOCK){
     //access myTable here...
}

Только установка поля в качестве конечного значения предотвратит любые сменные изменения изнутри или снаружи блокировки. Можно сказать, что последнее представляет собой ошибочное поведение Java, и ссылки также должны быть заблокированы, вызвав синхронизацию. Но так оно и должно быть, иначе код станет недостижимым.

Ну, для этой цели потребуется, по крайней мере, какой-то новый вид поля типа private guardable Object o;.;)

EDIT:

Здесь тестовый сценарий в классе Test (обратите внимание, что конструкторы загружают потоки довольно непредсказуемыми, поэтому поэтому wait (1000)... но это только означает, что это тест, обычно вы не должны запускать потоки в конструкторах вообще ):

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();
}

Результаты:

First: [email protected]
Second: [email protected]   locked!
Thread Thread[Thread-0,5,main]    waiting!
First: [email protected]
Second: [email protected]    locked!
Thread Thread[Thread-1,5,main]    waiting!

В строке 5 вы можете видеть, что блокировка не устраняет эталонное изменение. Затем тестовый случай был изменен на:

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){
    ...

Удовлетворение этого результата:

First: [email protected]
Second: [email protected]    locked!
Thread Thread[Thread-0,5,main]    waiting!
First: [email protected]

Здесь второй поток ожидает получения LOCK, и это именно то, что мы хотели.