【问题标题】:Multithreaded correctness: Using synchronized block多线程正确性:使用同步块
【发布时间】:2012-02-05 22:06:37
【问题描述】:

我正在使用 CMU Sphinx 语音识别器库 (Link to source),它使用了 synchronized 块。

来自 RecognizerTask 的一个示例块:

Event mailbox;

[...]

public void start() {
    synchronized (this.mailbox) {
        this.mailbox.notifyAll();
        this.mailbox = Event.START;
    }
}

代码可以正常运行,但是 BugFinder 会发出以下警告:

错误:在 RecognizerTask.mailbox 上进行同步是徒劳的 守护它

这个方法在一个看起来像 试图防止同时更新该字段。但 保护一个字段会锁定被引用的对象,而不是 场地。这可能无法提供您需要的互斥,以及其他 线程可能正在获取被引用对象的锁(对于其他 目的)。这种模式的一个例子是:

private Long myNtfSeqNbrCounter = new Long(0);
private Long getNotificationSequenceNumber() {
     Long result = null;
     synchronized(myNtfSeqNbrCounter) {
         result = new Long(myNtfSeqNbrCounter.longValue() + 1);
         myNtfSeqNbrCounter = new Long(result.longValue());
     }
     return result;
 }

说实话,我不太了解错误描述以及在这种情况下应该有什么问题。 全局变量不是字段吗?如果没有,我该如何改进代码?

/edit:这是唯一调用Event.wait()的部分:

Event todo = Event.NONE;
        synchronized (this.mailbox) {
            todo = this.mailbox;
            /* If we're idle then wait for something to happen. */
            if (state == State.IDLE && todo == Event.NONE) {
                try {
                    //Log.d(getClass().getName(), "waiting");
                    this.mailbox.wait();
                    todo = this.mailbox;
                    //Log.d(getClass().getName(), "got" + todo);
                } catch (InterruptedException e) {
                    /* Quit main loop. */
                    //Log.e(getClass().getName(), "Interrupted waiting for mailbox, shutting down");
                    todo = Event.SHUTDOWN;
                }
            }
            /* Reset the mailbox before releasing, to avoid race condition. */
            this.mailbox = Event.NONE;
        }

这段代码实际上也使用了synchronized 语句。使用它是否有意义?

【问题讨论】:

    标签: java android multithreading synchronized


    【解决方案1】:

    同步块“捕获”给定对象的锁,在您的情况下是mailbox 表示的对象。一旦您将变量mailbox 更改为指向不同的对象,其他线程就可以毫无问题地“捕获”该对象的锁,因为它没有被占用。

    请注意,锁是针对对象的,而不是针对引用的!

    现在,考虑以下 [伪代码]:

    synchronised (myObject) { 
      myObject = new Object();
      i += 5; //assume i is an instance variable
    }
    

    实际上这里没有锁!每个线程都在锁块中创建一个新对象,对i的修改是不同步的!

    【讨论】:

    • 这就是为什么我们需要创建一个最终对象并将其用于锁定。
    • 非常感谢您的回答!请查看我更新的问题 - 您是否建议完全删除 synchronized
    【解决方案2】:

    我认为它不适用于您的情况。您调用了notifyAll(),这意味着在其他线程的代码中的某处有一个匹配的wait() 调用:

    synchronized (this.mailbox) {
        this.mailbox.wait();        
    }
    

    表示其他线程将在等待通知时放弃锁。

    您的代码检查器可能对以下行感到困惑:

    this.mailbox = Event.START;
    

    意味着您可能正在同时修改此对象,这样如果另一个线程尝试获取this.mailbox 上的锁,它将看到不同的对象。但是,我确实认为,因为:

    1. this.mailbox 全局可见
    2. 引用的分配是原子的
    3. 锁生成栅栏

    所有线程都应该始终拥有同步对象的更新视图。

    【讨论】:

    • 你是对的。警告是正确的,同步不会像大多数人认为的那样。但在这种情况下,这并不重要。线程只在一个无关紧要的地方持有“错误”的锁——“不正确”锁定的代码不需要锁,因为它原子地分配了一个引用。
    • 非常感谢您的回答!请查看我更新的问题 - 您是否建议完全删除 synchronized
    • 实际上你不能,因为对对象的等待或通知/通知所有调用必须在同一对象的同步块内。
    • 非常感谢您的回答,帮助很大:)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-27
    • 2014-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-11-03
    相关资源
    最近更新 更多