【问题标题】:When a lock holds a non-final object, can the object's reference still be changed by another thread?当锁持有一个非最终对象时,该对象的引用仍然可以被另一个线程更改吗?
【发布时间】:2014-02-22 21:09:55
【问题描述】:

当一个对象需要同步时,如果它没有设置为非最终的,IDE 会报错(因为它的引用不是持久的):

private static Object myTable;
....

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

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

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

【问题讨论】:

    标签: java multithreading locking synchronized final


    【解决方案1】:

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

    如果您的意思是“另一个线程是否可以更改 myTable 变量的值,答案是“绝对”......假设有一个允许这样做的代码路径。它是一个私有变量,所以你应该能够找出所有可以改变值的代码。

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

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

    【讨论】:

    • 对,就是这个意思,大概标题应该是:“当一个锁持有一个非final变量的对象时,这个变量的值还能被另一个线程改变吗?” 一切都是为了找到合适的词,对吧? ;)
    • @Marcus:嗯,我还是不会这么说。您用于获取锁的引用 happened 来自一个变量,但这只是因为它是当时该变量的值。可能存在具有相同引用的其他变量。
    • 所以再试一次:“当一个锁持有从一个非final变量获取的对象时,这个变量的值还能被另一个线程改变吗?”
    • @Marcus:至少更好,但锁不持有对象——它是与对象关联的锁(或监视器),它是持有锁的线程。更多的是“当一个线程为其引用是通过非最终变量获得的对象持有锁时,该变量的值仍然可以被另一个线程更改吗?”但我可能不会用它作为标题:)
    【解决方案2】:

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

    private static Object myTable;
    private final static Object LOCK;
    ....
    
    synchronized(LOCK){
         //access myTable here...
    }
    

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

    嗯,至少需要一些像private guardable Object o; 这样的新字段来达到这个目的。 ;)

    编辑:

    这是 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: 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,这正是我们想要的。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-03-09
      • 2015-06-14
      • 1970-01-01
      • 2023-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多