当一个锁持有一个非最终对象时,该对象的引用是否仍然可以被另一个线程更改?

时间:2014-01-30 14:04:44

标签: java multithreading locking synchronized final

当一个对象需要同步时,IDE会抱怨它是否设置为非final(因为它的引用不是持久的):

private static Object myTable;
....

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

我们都知道如果持有锁的线程改变非最终对象的引用,IDE会抱怨阻止另一个线程进入受保护的块。

但是,当线程A保存同一个对象的锁时,同步对象的引用是否也可以被另一个线程B更改?

2 个答案:

答案 0 :(得分:5)

  

但是,当线程A保存同一个对象的锁时,同步对象的引用是否也可以被另一个线程B更改?

如果你的意思是“另一个线程可以改变myTable变量的值,那么答案就是”绝对“......假设有一个允许的代码路径。它是一个私有变量,所以你应该是能够找到所有可以改变价值的代码。

持有锁只会阻止另一个线程获取相同的锁。它对哪些代码本身可以访问哪些变量没有任何影响。

作为旁注,区分对象变量和变量的值(它是一个引用,而不是一个对象)很重要。 。所以没有“最终对象”这样的东西 - 只有变量(以及类和方法)可以是最终的。同样地,没有“同步对象”这样的东西,你不能改变“对象的引用” - 你可以改变变量的值,使它成为对不同对象的引用。在思考这里发生的事情时,在脑海中明确区分这些区别可能会有所帮助。

答案 1 :(得分:1)

是的,它可以。所有非最终变量/字段都可以随时更改其对对象的值/引用,无论对象是在受保护块内还是外。因此,您始终需要将第三方锁与非最终字段结合使用:

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

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

仅将字段设置为最终字段会阻止来自 锁定内的任何引用更改。可以说后者是一种错误的Java行为,并且应该通过调用synchronized来锁定引用。但这就是它需要的方式,否则代码将变得无法维护。

嗯,为此目的,至少需要一些像private guardable Object o;这样的新领域。 ;)

修改

这是测试类中的一个测试用例(请注意,构造函数加载线程非常不可预测,因此等待(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: 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!

您可以在第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: model.TestObject@150bd4d
Second: model.TestObject@150bd4d    locked!
Thread Thread[Thread-0,5,main]    waiting!
First: model.TestObject@150bd4d

这里第二个线程等待获取LOCK ,这正是我们想要的。