【问题标题】:StringBuilder modified by multiple threads多线程修改的StringBuilder
【发布时间】:2013-06-04 21:17:06
【问题描述】:

我要问的问题与Difference between StringBuilder and StringBuffer 有关,但不一样。我想看看如果 StringBuilder 同时被两个线程修改会发生什么。

我写了以下类:

public class ThreadTester
{
    public static void main(String[] args) throws InterruptedException
    {
        Runnable threadJob = new MyRunnable();
        Thread myThread = new Thread(threadJob);
        myThread.start();

        for (int i = 0; i < 100; i++)
        {
            Thread.sleep(10);
            StringContainer.addToSb("a");
        }

        System.out.println("1: " + StringContainer.getSb());
        System.out.println("1 length: " + StringContainer.getSb().length());
    }
}

public class MyRunnable implements Runnable
{
    @Override
    public void run()
    {
        for (int i = 0; i < 100; i++)
        {
            try
            {
                Thread.sleep(10);
            }
            catch (InterruptedException e)
            {
                e.printStackTrace();
            }
            StringContainer.addToSb("b");
        }

        System.out.println("2: " + StringContainer.getSb());
        System.out.println("2 length: " + StringContainer.getSb().length());
    }
}

public class StringContainer
{
    private static final StringBuffer sb = new StringBuffer();

    public static StringBuffer getSb()
    {
        return sb;
    }

    public static void addToSb(String s)
    {
        sb.append(s);
    }
}

最初我在 StringContainer 中保留了一个 StringBuffer。由于 StringBuffer 是线程安全的,一次只能有一个线程可以附加到它,因此输出是一致的 - 两个线程都将缓冲区的长度报告为 200,例如:

1: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
1 length: 200
2: abababababababababbaabababababababbaababababababababababababbabaabbababaabbaababababbababaabbababaabababbaabababbababababaababababababababbababaabbaababbaababababababbaababbababaababbabaabbababababaab
2 length: 200

或者其中一个报告了 199 个,另一个报告了 200 个,例如:

2: abbabababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababab
2 length: 199
1: abbababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababababa
1 length: 200

关键是最后完成的线程报告长度为 200。

现在,我将 StringContainer 更改为 StringBuilder 而不是 StringBuffer,即

public class StringContainer
{
    private static final StringBuilder sb = new StringBuilder();

    public static StringBuilder getSb()
    {
        return sb;
    }

    public static void addToSb(String s)
    {
        sb.append(s);
    }
}

我希望某些写入会被覆盖,这种情况正在发生。但是StringBuilder的内容和长度有时不匹配:

1: ababbabababaababbaabbabababababaab
1 length: 137
2: ababbabababaababbaabbabababababaab
2 length: 137

如您所见,打印的内容只有 34 个字符,但长度为 137。为什么会出现这种情况?

@Extreme Coders - 我刚刚又进行了一次测试:

2: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1: ababbabababaabbababaabbababaababaabbaababbaaababbaabbabbabbabababbabababbbabbbbbabababbaabababbabaabaaabaababbaabaababababbaabbbabbbbbababababbababaab
1 length: 150
2 length: 150

Java 版本:1.6.0_45,我使用的是 eclipse 版本: 面向 Web 开发人员的 Eclipse Java EE IDE。 版本:Juno Service Release 2 内部版本号:20130225-0426

更新 1: 我在 eclipse 之外运行了这个,现在它们似乎匹配了,但有时我会收到 ArrayIndexOutOfBoundsException:

$ java -version
java version "1.6.0_27"
OpenJDK Runtime Environment (IcedTea6 1.12.5) (6b27-1.12.5-0ubuntu0.12.04.1)
OpenJDK Server VM (build 20.0-b12, mixed mode)

$ java ThreadTester
1: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
1 length: 123
2: ababbbbbabbabababababaababbaabbbaabababbbababbabababbabbababbbbbbabaabaababbbbbbabbbbbaabbaaabbbbaabbbababababbbbabbababab
2 length: 123

$ java ThreadTester 
2: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
2 length: 115
1: abbabaabbbbbbbbbababbbbbabbbabbbabaaabbbbbbbabababbbbbbbbbabbbbbbbababababbabbbbaabbbaaabbabaaababaaaabaabbaabbbb
1 length: 115

$ java ThreadTester 
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException
    at java.lang.System.arraycopy(Native Method)
    at java.lang.String.getChars(String.java:862)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:408)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at StringContainer.addToSb(StringContainer.java:14)
    at ThreadTester.main(ThreadTester.java:14)
2: abbbbbbababbbbabbbbababbbbaabbabbbaaabbbababbbbabaabaabaabaaabababaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
2 length: 114

从 Eclipse 运行时也会发生 ArrayIndexOutOfBoundsException。

更新 2: 有两个问题正在发生。 StringBuilder 的内容与长度不匹配的第一个问题仅在 Eclipse 中发生,而不是在我在命令行中运行时发生(至少我在命令行上运行它的 100 多次从未发生过)。

ArrayIndexOutOfBoundsException 的第二个问题应该与 StringBuilder 类的内部实现有关,该类保留一个字符数组并在扩展大小时执行Arrays.copyOf。但是无论执行顺序是什么,在扩展大小之前写入是如何发生的仍然让我感到震惊。

顺便说一句,我倾向于同意@GreyBeardedGeek 的回答,即整个练习都是在浪费时间:-)。有时我们只能看到症状,即某些代码的输出,并想知道出了什么问题。这个问题先验声明了两个线程正在修改一个(非常有名的)线程不安全对象。

更新 3: 这是来自Java Concurrency in Practice p 的官方回答。 35:

  • 在没有同步的情况下,编译器、处理器和 运行时可以对其中的顺序做一些非常奇怪的事情 操作似乎正在执行。尝试推理顺序 哪些记忆动作“必须”在不充分同步的情况下发生 多线程程序几乎肯定是不正确的。

  • 并发程序同步不足的原因是 非常困难。

p 的书中还有一个很好的例子NoVisibility。 34.

【问题讨论】:

  • 这很奇怪,在我的机器上它打印出199200 并且StringBuilder 的大小与字符数相同。我已经运行了多次,但每次都得到相同的结果
  • 这也和IDE有关。最初我使用 IntelliJ,它总是给出199200 的长度。接下来我在 BlueJ 中运行它,每次运行它都会给出可变长度。似乎 IntelliJ 在处理多线程方面比其他 IDE 更好。
  • 是否有机会从命令行 IDE 获得结果? :)
  • @A.J.添加了命令行 IDE 输出 :)
  • @ExtremeCoders:IntelliJ 运行与所有其他 IDE 相同的 JVM。它不能比其他 IDE 更好,因为它不运行程序:JVM 可以。此外,在这方面没有什么比这更好的了:结果尚未确定。获得正确结果的唯一方法是正确同步对 StringBuilder 的所有访问。

标签: java multithreading synchronization stringbuilder stringbuffer


【解决方案1】:

当多个线程同时访问时,非线程安全类的行为定义为“未定义”。

在这种情况下确定确定性行为的任何尝试都是,恕我直言, 只是浪费时间。

【讨论】:

  • 尽管如此,我认为这种打印长度为 137 且实际长度为 50 的情况即使具有“未定义”行为也是不可能的。
【解决方案2】:

打印的字符数和打印的长度之间的差异来自于在另一个线程仍在运行时打印值。该错误与同步相关,是由两个线程同时尝试修改同一个对象引起的。

在第一个和第二个println 之间,另一个线程完成了一个附加循环并更改了缓冲区的内容。

【讨论】:

    猜你喜欢
    • 2011-04-16
    • 1970-01-01
    • 2017-03-05
    • 1970-01-01
    • 1970-01-01
    • 2010-10-12
    • 1970-01-01
    • 1970-01-01
    • 2012-08-15
    相关资源
    最近更新 更多