【问题标题】:Why is nameList.add not synchronized?为什么 nameList.add 不同步?
【发布时间】:2014-10-04 17:56:51
【问题描述】:

以下是 Oracle Java 教程中的一小段文字:

“同步语句

创建同步代码的另一种方法是使用同步语句。与同步方法不同,同步语句必须指定提供内在锁的对象:

public void addName(String name) {
    synchronized(this) {
        lastName = name;
        nameCount++;
    }
    nameList.add(name);
}

在本例中,addName 方法需要同步对lastName 和nameCount 的更改,但还需要避免同步调用其他对象的方法。 (从同步代码中调用其他对象的方法可能会产生问题,这在 Liveness 部分中进行了描述。)如果没有同步语句,就必须有一个单独的、非同步的方法来调用 nameList.add。

我理解他们关于 Synchronized 提供的灵活性的观点。但是为什么 Oracle 决定 nameList.add 不需要同步呢?更一般地说,我如何确定哪些对象方法需要同步,哪些不需要?

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    同步有其价格和性能。一般的经验法则(JDK 本身也遵循,在这种情况下也是如此)是除非绝对需要,否则不要同步。

    由于在很多情况下您希望在不需要同步的情况下将元素添加到列表(例如,如果所述列表是局部变量),因此未将其定义为同步。当您需要同步此类操作时,您始终可以使用CopyOnWWriteArrayList,或显式同步您的访问权限。

    【讨论】:

    • 我认为你误解了这个问题(就像我第一眼看到的那样)。这不是问为什么ArrayList 和其他集合不同步。而是问为什么,在代码示例中,nameList.add() 行不包含在 synchronized 块中。
    • C-A 是正确的。在我看来,除非 nameList 是线程安全的,否则代码将不安全。我认为如果 nameList 不是线程安全的,它必须在同步块中。我对么?另外,nameList 怎么可能是一个局部变量?它显然不是 addName 中的局部变量。如果它是任何其他方法中的局部变量,它将不在范围内。在哪些情况下您希望将元素添加到列表而不需要同步?
    【解决方案2】:

    根据您知道线程将访问的内容进行同步

    • 如果他们尝试访问列表,则同步 getter。在这种情况下,Oracle 仅同步对方法的调用。
    • 如果他们试图修改列表,则将引用同步到块内的列表!

    所以:

    synchronized(this.nameList){
     //now I'm safe to modify
      nameList.add(name);
     } 
    

    【讨论】:

    • 即使其他线程无法访问或修改列表,如果nameList不是线程安全的集合,上面的代码不会导致内存一致性错误吗?
    【解决方案3】:

    你可以像这样创建一个线程安全的字符串数组列表:

    ArrayList<String> list = Collections.synchronizedList(new ArrayList<String>());
    

    add() 和 remove() 方法是线程安全的。如果您不需要遍历元素就可以了。

    注意:您必须在迭代时手动同步返回的集合

    synchronized (list) {
      Iterator i = list.iterator(); // Must be in the synchronized block
      while (list.hasNext())
         yourmethod(list.next()); // Your logic on elements...
    }
    

    在使用之前阅读这里的文档:Synchronized Collection

    【讨论】:

    • 我看到 nameList 可能是线程安全的。但如果它是一个 ArrayList,它就不是线程安全的。如果它被声明为 ArrayList,代码是否安全?我不确定,但我不这么认为。如果 nameList 是 ArrayList,即使 nameList 不可访问,是否也不会出现内存一致性错误?
    • @ClarkS 声明线程安全 ArrayList 的方法是我在上面第 1 行中向您展示的方法。如果您将其简单地声明为 ArrayList list=new new ArrayList();那么线程安全就得不到保证。只需使用上面的 Collections.synchronizedList 声明它,就可以安全添加。
    猜你喜欢
    • 2016-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多