为什么我的同步代码块似乎忽略了同一个对象上的另一个等待同步块?

时间:2014-01-14 04:00:16

标签: java android thread-synchronization

我对Java中同步代码块的行为感到困惑。有一种我正在观察的行为,我只是没有得到。请考虑以下代码(我将重用我提出的另一个问题here中的代码示例,因为结构是相同的):

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", "outer method");
            foo.bar();    // Has some effect on foo
        }
    }

    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) {
                // There is some timer management code here.
                // It executes on the order of microseconds.

                synchronized(lock) {
                    Log.d("thread", "loop");
                    foo.blah();    // Modifies foo
                    // Imagine some time intensive (milliseconds)
                    // drawing method calls here
                }
            }
        }

    }
}

这种方法可能看似令人费解;请注意,我是从一个Android示例应用程序中调整了这个,这个问题不是重新设计我的代码结构,除非有必要解决这个问题。请记住,线程中的while循环是一个绘图循环,我的游戏逻辑有时会进入并调用modifyFoo方法(Android SDK用户可能会认为这是对LunarLander的修改例)。对modifyFoo的调用如下所示:

Log.d("activity", "calling modifyFoo");
modifyFoo();

调用方法时,日志输出中会显示意外行为。我期待这样的事情:

thread: loop
thread: loop
thread: loop < `modifyFoo` method called during this loop iteration
activity: called modifyFoo
outer: outer method
thread: loop
thread: loop

但我看到结果更像这样(编辑:使用时间戳复制实际日志)

01-23 04:34:28.303: D/thread(399): loop
01-23 04:34:28.335: D/thread(399): loop
01-23 04:34:28.350: D/activity(399): calling modifyFoo
01-23 04:34:28.366: D/thread(399): loop
01-23 04:34:28.381: D/thread(399): loop
01-23 04:34:28.413: D/thread(399): loop
01-23 04:34:28.428: D/outer(399): outer method
01-23 04:34:28.436: D/thread(399): loop
01-23 04:34:28.460: D/thread(399): loop

请注意,在循环中在synchronized块的开头和结尾添加其他日志语句,确认在执行此块时正在调用modifyFoo方法。

调用modifyFoo和服务之间的空间(线程循环迭代次数)可能非常长,但有时候,延迟太短以至于无法察觉(实际上似乎是随机的) 。它表现为一个冻结的UI,因为外部方法在我的UI线程上运行。显然,这是一个问题。在线程循环中在同步块外部插入1ms Thread.sleep(1)似乎可以解决问题,所以我最初的想法是“哦,while循环中的同步块没有给外部线程足够的时间来调用modifyFoo方法”。但是,基于某些日志记录,modifyFoo似乎总是在此延迟期间执行,这是我所期望的。

所以问题是:这里发生了什么?一旦外部UI线程在modifyFoo中的同步块中等待,为什么内部线程在modifyFoo使其“脱离机翼”之前回到锁定位置?无法同步块“自行排队”?

2 个答案:

答案 0 :(得分:1)

每当任何synchronized完成它的执行时,它会通过说明通知所有其他线程等待锁定同一个对象。

“我正在释放对此对象的锁定,任何一个人都可以开始执行。”

现在选择哪个线程将被锁定是随机的。(我想说未知)。

在你的例子中:

假设线程循环正在执行,并且调用了modifyFoo方法。

运行modifyFoo的线程将等待直到锁定被释放,因为while循环因为while循环而被放入另一个线程等待锁定同一个对象并且其中任何一个被挑选。现在你的modifyFoo仍然在等待这个循环结束,同样的事情再次发生。

因此,经过一些线程循环的执行(这将是随机的),修改foo获取并执行的机会。

答案 1 :(得分:1)

这里要记住的是,一旦线程阻塞等待关键部分变为空闲,那么由调度程序决定何时产生,并且没有任何其他代码告诉其他线程如何表现,例如wait()notify(),调度程序没有义务“很好”,原谅双关语。见Difference between Synchronized block with wait/notify and without them?

在Java 1.5之前的那一天,使用synchronized/wait/notify是必要的,但在使用java.util.concurrency包的现代代码中,建议使用以下范式进行并发以进行锁定错误的可能性较小,避免死锁或饥饿:http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/Condition.html