提问者:小点点

为什么我的StringBuffer变量没有同步。其中as StringBuilder变量的行为与synchronized相同


我试图理解StringBuilder和StringBuffer之间的区别。下面这个程序的目标是让两个线程(Jack和Jill)竞争改变StringBuffer和StringBuilder的值。如果原始值已经被修改,那么线程将不会修改该变量。为什么我的StringBuffer变量没有同步。其中as StringBuilder变量的行为与synchronized相同

StringBuffer:Jack赢了StringBuilder:Jack wonJill赢了

其中实际输出是相反的。

public class StringBufferIsThreadSafe{
    
    public static void main(String[] args) throws InterruptedException {
        
        Hello hello = new Hello();
        
        Thread jackThread = new Thread(new Jack(hello.strBuf, hello.strBuilder));
        Thread jillThread = new Thread(new Jill(hello.strBuf, hello.strBuilder));
        
        jackThread.start();
        jillThread.start();
        jackThread.join();
        jillThread.join();
        
        System.out.println("StringBuffer: "+hello.strBuf);
        System.out.println("StringBuilder: "+hello.strBuilder);
    }
}
class Jack implements Runnable{
    
    private StringBuffer strBuf;
    private StringBuilder strBuilder;
    
    public Jack(StringBuffer strBuf, StringBuilder strBuilder) {
        this.strBuf = strBuf;
        this.strBuilder = strBuilder;
    }

    @Override
    public void run() {
        try {
            Thread.sleep(3000);
            
            if(this.strBuf.toString().equals("")) {
                this.strBuf.append("Jack won");
            }
                
            if(this.strBuilder.toString().equals("")) {
                this.strBuilder.append("Jack won");
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
}
class Jill implements Runnable{
    
    private StringBuffer strBuff;
    private StringBuilder strBuilder;
    
    public Jill(StringBuffer strBuff, StringBuilder strBuilder) {
        this.strBuff = strBuff;
        this.strBuilder = strBuilder;
    }
    

    @Override
    public void run() {
        
        try {
            Thread.sleep(3000);
            if(this.strBuff.toString().equals("")) {
                this.strBuff.append("Jill won");
            }
            
            if(this.strBuilder.toString().equals("")) {
                this.strBuilder.append("Jill won");
            }
            
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        
    }
    
}
class Hello {
    StringBuffer strBuf = new StringBuffer();
    StringBuilder strBuilder = new StringBuilder();
    
    public Hello() {
        this.strBuf.append("");
        this.strBuilder.append("");
    }
}

共1个答案

匿名用户

我想你误解了‘内部同步’的意思。

“内部同步”不是巫毒魔法。StringBuffder中的代码根本无法影响您的代码。当您写以下内容时,这将成为一个问题:

if (strBuffer.toString().equals("")) {
    strBuffer.append("Jill won");
}

这里没有任何保证,具体来说,您似乎认为整个操作现在在某种程度上是原子的(在此之后,strBuffer将不可能包含Jack wonJill Won)。但事实并非如此。完全有可能在这段代码之后,内容现在是Jack wonJill Won

这个问题有两种解决方案:

[1]显式使用锁

synchronized (strBuf) {
    if (strBuf.toString().isEmpty()) strBuf.append("Jill won");
}

效果很好。这样,您实际上不再需要内部同步方面了。你已经搞定了。

因此,忘记StringBuffer而只使用StringBuilder的原因如下:

  • 认为StringBuffer已经过时,因为它已经过时了。java核心库不会被标记为已弃用或删除,即使过时也不会被删除。参见Vector、Hashtable、java.util.Date等。它们只是没有--openjdk开发团队发现“10年前编写的代码最好仍然可以编译,并且在今天的Javac上编译时做同样的事情”非常重要,所以他们没有这样做。所以不要犯这样的错误:“哦,StringBuffer是一个存在的东西,没有明确地标记为过时,因此它一定有一定的意义”。不,没有。永远不要使用它。
  • 内部同步几乎完全没用,请查看您的代码片段,它没有达到您所期望的/想要的效果。
  • java社区作为一个整体只是到处使用StringBuilder。当有两种不同的方法来做同样的事情,并且它们在所需的努力、性能和代码风格上似乎是相同的(就像这里所说的那样),您应该做社区喜欢做的事情。它降低了团队的学习曲线,避免了令人讨厌的风格差异(您以一种方式编写,而您的伙伴以另一种方式编写,每次你们中的一个人读到另一个人的代码时,都无缘无故地看起来很奇怪),并且走老路是您如何获得其他库中bug问题较少的代码,使用更多的库,并获得性能优势的方法,因为致力于使java快速的OpenJDK工程师专注于公共代码模式。当它在罗马的时候,就像罗马的规则一样。

相关问题