【问题标题】:Synchronized objects accessed from unsynchronized methods从非同步方法访问的同步对象
【发布时间】:2014-02-03 13:43:07
【问题描述】:

假设我有一个类,其中有一个 StringBuffer 声明为成员变量。两个线程正在尝试像下面这样操作对象

public class SomeService {

    private StringBuffer sb = new StringBuffer();

    public void printName(String name) {
        sb.append(name);
        System.out.println(sb);
    }
}

public class StringBufferSynchronizationTest implements Runnable {

    private SomeService service = new SomeService();

    public StringBufferSynchronizationTest() {

        Thread thread = new Thread(this);
        thread.start();
    }

    public static void main(String[] args) {

        new StringBufferSynchronizationTest().service.printName("oops");
    }

    @Override
    public void run() {
        service.printName("java");
    }
}

我得到了这个输出

oopsjava
oopsjava

我以为我会得到

oops
oopsjava

作为输出。当我同步 printName 方法时,我得到了第二个输出。

所以我明白,即使我使用同步类,我也必须同步使用同步类的块/方法。我说的对吗?

【问题讨论】:

  • 是的,你必须使用同步方法
  • 首先你必须知道你想要什么,然后你开始思考如何得到它。 StringBuffer 上的每个方法调用都是自己同步的,这显然与单独方法调用的交错无关。
  • 我相信如果你运行足够多次,你最终会得到预期的输出。这是一个竞争条件。
  • 那么同步 StringBuffer 有什么用。我可以在我的代码中同步块/方法。是一样的吧?
  • @Cruncher 是的,我也相信这一点。但我总是希望输出类似于第二个的输出。

标签: java multithreading synchronization


【解决方案1】:

StringBuffer 保证只有一个线程可以进入 append 或同一实例的任何其他方法。但这就是全部,没有更多的保证了。

【讨论】:

  • 好吧,它也保证了如果两个线程在同一个实例上同时调用append(或其他同步的StringBuffer方法),就会有happens-before关系。 :-)
  • StringBuffer 保证其方法以顺序一致的方式执行。这包括互斥和发生之前。
【解决方案2】:

是的,StringBuffer 是同步的,如果你想要预期的结果,还同步函数 printName。 sb.append(name); 和 System.out.println(sb); 之间可能会发生上下文切换,尤其是与慢 IO 有关。

public synchronized void printName(String name) {
    sb.append(name);
    System.out.println(sb);
}

【讨论】:

    【解决方案3】:

    这取决于你想要完成什么。

    让我们看看printName

    public void printName(String name) {
        sb.append(name);
        System.out.println(sb);
    }
    

    由于 sb 处于同步状态,sb.append(name) 只有 1 个线程在对象可变状态上运行。在您的示例中,这可以防止字符串的值

    oojavaps
    ojopsava
    

    等等。但是,由于您的 printName 方法未同步,因此您无法保证跨 2 个线程调用 printName 中的 2 个方法的顺序。

    理解它的最简单方法可能是想出导致输出的执行顺序

    oopsjava
    oopsjava
    

    最初sb 是空字符串

    假设主线程执行sb.append(name),将sb留给oops,但是它在执行println之前就被抢占了

    构造函数线程接管并执行整个方法,首先将java附加到sb得到oopsjava中sb然后打印输出,得到

    oopsjava
    

    然后主线程执行,打印sb得到

    oopsjava
    

    我在这里有一个输出示例不正确,@Cruncher 在 cmets 中指出了这一点,我将其删除。

    【讨论】:

    • This prevents Strings, in your example, of the values---实际上,它可以防止比这更深层次的麻烦,比如破坏StringBuffer的内部不变量。这样的事情将是不同步的方法调用的合法结果。
    • 我看不出oops\njava 是如何可能的结果。 StringBuffer 是同步的,你追加。它无法通过两个追加调用,并“删除”此代码中的第一个追加。
    • @MarkoTopolnik 你cmets 错过了正在提出的观点。我无意列出所有可能的错误模式是 StringBuffer were not synchronized。
    • 除了你的观点之外,我也提出了自己的观点,因为我认为这一点对于理解数据竞赛的内容很重要。仅考虑您指出的轻微情况而对代码进行推理会导致同步不足和代码损坏。
    • @MarkoTopolnik 啊,我明白了,你也说得对
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2013-08-06
    • 2011-11-28
    • 1970-01-01
    相关资源
    最近更新 更多