StringBuilder由多个线程修改

时间:2013-06-08 19:44:18

标签: java multithreading synchronization stringbuilder stringbuffer

我提出的问题与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 建造ID: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。

2 个答案:

答案 0 :(得分:8)

当多个线程并发访问时,非线程安全类的行为根据定义是“未定义的”。

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

答案 1 :(得分:1)

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

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

相关问题