当两个监视器字段引用同一个对象时,为什么两个同步块表现得像我提供了不同的监视器对象?

时间:2014-01-13 19:12:29

标签: java reference thread-synchronization

我编写了一个带有扩展Thread的内部私有类的类。我的外部类启动此线程的实例,并且线程在循环内访问外部类的字段。但是,外部代理可以调用​​外部类的方法来修改外部类的字段。这些方法必须与线程内的循环同步,以便修改不会干扰循环。

我一直在使用外部类(Android SurfaceHolder)字段上的“synchronized”块进行同步,将此对象传递到内部类并将其作为内部类中的字段存储,然后同步线程循环中的内部类字段。然而,这导致了当内部类应该锁定时外部类方法将运行的行为!我尝试删除内部字段并将内部类锁定在与外部类完全相同的字段上,一切正常。

所以,这就是问题:我通过检查==运算符并查看字符串表示来验证内部和外部字段指向的对象是同一个对象,那么为什么同步块在这种情况下表现出来好像我在使用两个不同的物体?也许我对同步块的工作方式有一个根本的误解?

修改

好吧,我得到了很多赞成票,但是评论者似乎只想要更多的细节,我决心要明白我没有得到什么。我会回复每条评论。在这里,开始,是我正在做的一个例子:

class Outer {
    private Object lock;
    private Foo foo;        

    public Outer() {
        lock = new Object();

        // The thread is actually started in an Android callback method,
        // but I'll start it here as a demonstration
        InnerThread thread = new InnerThread(lock);
        thread.setRunning(true);
        thread.start();
    }

    private void modifyFoo() {
        synchronized(lock) {
            Log.d("outer", "locked outer");
            foo.bar();    // Has some effect on foo
        }
        Log.d("outer", "released outer");
    }

    private class InnerThread extends Thread {

        private volatile boolean running = false;
        private Object lock;

        public InnerThread(Object lock) {
            this.lock = lock;
        }

        private void setRunning(boolean running) {
            this.running = running;
        }

        @Override
        public void run() {
            while(running) {
                synchronized(lock) {
                    Log.d("inner", "locked inner");
                    foo.blah();    // Has some effect on foo
                }
                Log.d("inner", "released inner");
            }
        }

    }
}

意外行为是当我在线程运行时调用modifyFoo()方法时,我看到以下日志输出:

locked inner
released inner
locked inner
released inner
locked inner
 locked outer
 released outer
released inner

一个响应表明“你永远不应该扩展线程”和“你似乎在一个字段和外部对象本身中有一个对象”。首先,我正在扩展Thread,因为它是Android SurfaceView示例中使用的方法。我不知道如何覆盖run()方法;我应该将runnable传递给线程吗?其次,正如我所理解的那样,内部和外部类中的lock字段都保存对在lock = new Object();行上创建的同一实例的引用。我不是问这应该如何构建;我特别询问为什么同步块看起来正在查看与不同对象相同的对象的这两个引用。

要添加一些上下文,这适用于使用Android的SurfaceView类的项目。我尽可能地关注Android提供的示例LunarLander项目,其中用于锁定的SurfaceHolder对象被传递到线程构造函数并通过引用存储在其中。这可能是奇怪结构的原因。

1 个答案:

答案 0 :(得分:3)

  

当两个监视器字段引用同一个对象时,为什么两个同步块的作用就像我提供了不同的监视器对象一样?

如果没有看到代码,我会认为它们确实是不同的对象,即使你认为它们应该是相同的。

  

我编写了一个带有扩展Thread的内部私有类的类。

你永远不应该扩展Thread。这导致您在代码中不想要的各种边缘情况。

  

我一直在使用外部类(Android SurfaceHolder)字段上的“synchronized”块进行同步,将此对象传递到内部类并将其作为内部类中的字段存储,然后同步线程循环中的内部类字段。但是,这导致了当内部类应该锁定时外部类方法将运行的行为!

听起来你有两个实例。您似乎在字段中有一个对象,外部对象本身。

  

我尝试删除内部字段并将内部类锁定在与外部类完全相同的字段上,一切正常。

这取消了我提到的两个。

  

也许我对同步块的工作方式有一个根本的误解?

您必须锁定同一个对象实例。您不锁定字段或类或方法。我建议创建一个锁定实例,它仅用于锁定,这就是你所使用的。这样,如果它是外部类的字段,则无需将其传递给内部类。

class Outer {
    final Object lock = new Object();

    public void method() {
        synchronized(lock) {
            // do something
            lock.notifyAll();
        }
    }

    class Inner implements Runnable {
         public void run() {
              while(!Thread.currentThread().isInterrupted()) {
                 synchronized(lock) {
                     lock.wait();
                     // check if anything changed.
                 }
              }
         }
    }
}

编辑:

  

我通过检查==运算符验证了内部和外部字段指向的对象是同一个对象

如果我复制了错误的引用对象以进行锁定并将其与自身进行比较,那么它也是如此。如果我复制一个引用并有另一个引用,我知道应该是相同的,为什么要首先复制它?

EDIT2:我就是这样写的。

class Outer {
    final Object lock = new Object();
    private Foo foo;        

    public Outer() {
        // The thread is actually started in an Android callback method,
        // but I'll start it here as a demonstration
        Thread thread = new Thread(new InnerRunnable());
        thread.start();
    }

    private void modifyFoo() {
        synchronized(lock) {
            Log.d("outer", "locked outer");
            foo.bar();    // Has some effect on foo
            Log.d("outer", "released outer");
        }
    }

    class InnerRunnable implement Runnable {
        private volatile boolean running = true;

        void setRunning(boolean running) {
            this.running = running;
        }

        @Override
        public void run() {
            while(running) {
                synchronized(lock) {
                    Log.d("inner", "locked inner");
                    foo.blah();    // Has some effect on foo
                    Log.d("inner", "released inner");
                }
            }
        }
    }
}

您可以选择放弃锁定,然后使用foo