3

我正在编写一个静态实用程序方法。我知道方法isEmpty()并且isNew()是线程安全的。在getTotal(...)我使用StringBuilder作为参数的方法中,与 String 和 int 一起使用。StringBuilder是可变的。getTotal()线程安全吗?如果是这样,请解释为什么即使 StringBuilder 是可变的。我不确定是否getCharge()是线程安全的,因为它正在调用方法getTotal()。有人可以判断它是否是线程安全的吗?

public class NewStringUtils {

    public static boolean  isEmpty(String s){
        return (s == null || s.trim().length()==0);
    }

    public static boolean isNew(String code){
        return( "1009".equals(code) || "1008".equals(code) );
    }
    //StringBuilder is  mutable  -- is the below method threadsafe 
    public static int getTotal(StringBuilder sb,String oldCode ,int a, int b){
        //Is it Threadsafe or not .If so  just bcz every thread invoking this method will have its own copy and other threads can't see its value ?? 
        int k =0;
        if("1011".equals(oldCode) && "1021".equals(sb.toString()) {
            k = a+b;
        }
        return k;
    }
     // is the below method threadsafe 
    public static int getCharge(String code,String oldCode,int charge1,int charge2){
        int total =0;
        StringBuilder sb = new StringBuilder("1021");
        if(!NewStringUtils.isEmpty(code)){

            if(NewStringUtils.isNew(code)){
                //here invoking a static method which has StringBuilder(Mutable) as a parameter
                total  = NewStringUtils.getTotal(sb,oldCode,charge1,charge2);
            }
        }
        return total;
    }
}
4

4 回答 4

5

getTotal is not thread-safe because it is possible that two or more than two threads are passing same reference of StringBuilder to the getTotal method as arguement and modifying the StringBuilder before passing it ...
And your getCharge is completely Thread-safe because here each thread is making local copy of StringBuilder Object in there own stack. So not to worry about thread safety of getCharge.
Here is the short Demo which is demonstrating why getTotal is not ThreadSafe:

class  ThreadSafe
{
    public static int getTotal(StringBuilder sb,String oldCode ,int a, int b)
    {
        int k =0;
        try
        {
            System.out.println(Thread.currentThread().getName() + " have sb as " + sb);
            Thread.sleep(100);//Added intentionally to show why it is not thread safe.
        }
        catch (Exception ex)
        {
            System.out.println(ex);
        }
        if("1011".equals(oldCode) && "1021".equals(sb.toString())) 
        {
            System.out.println(Thread.currentThread().getName()+" is within if loop");//Thread1 should be within this if block but it's not.
            k = a+b;
        }
        return k;
    }
    public static void main(String[] args) 
    {
        final StringBuilder sBuilder = new StringBuilder();
        Thread th1 = new Thread(new Runnable()
        {
            public void run()
            {
                sBuilder.append("1021");
                getTotal(sBuilder,"1011",10,20);
            }
        },"Thread1");
        Thread th2 = new Thread(new Runnable()
        {
            public void run()
            {
                sBuilder.append("22");
                getTotal(sBuilder,"1011",10,20);
            }
        },"Thread2");
        th1.start();
        th2.start();
    }
}

At my system , the output is:

Thread1 have sb as 1021
Thread2 have sb as 102122

Assuming that Thread1 started before Thread2 which indeed happened here(as seen in output), If getTotal would have been thread-safe then , the statement Thread1 is within if loop must have been printed in output. But it is not . WHY? Because after Thread1(with sb = "1021") went to sleep in getTotal method the thread is preempted by Thread2. Thread2 appended 22 to the existing StringBuilder object sBuilder(i.e the new value now is 102122). Again when sleep method is called for Thread2, it is preempted by Thread1. Thread1 goes to if construct , but till now the content of sBuilder is changed to 102122. So if condition becomes false for Thread1. And it does'nt print that line Thread1 is within if loop which we were expecting if we consider getTotal to be thread-safe. Hence it proves that getTotal is not Thread-safe.

How can we make getTotal ThreadSafe?

  1. By passing String instead of StringBuilder.
于 2013-03-28T19:28:25.620 回答
3

getTotal() 在可重入方面是线程安全的(即,它可以被多个线程同时调用)。然而,就正确完成其工作而言,如果另一个线程修改了“sb”参数,则 getTotal()不是线程安全的。这种有条件的安全性应在该方法的文档中明确说明。

如果 getTotal() 是私有的而不是公共的并且仅由 getCharge() 调用,那么它将是无条件线程安全的。

于 2013-03-28T19:46:08.550 回答
1

以下是凯文想要提出的建议。这是线程安全的:

//StringBuilder is  mutable  -- is the below method threadsafe 
public static int getTotal(String sb,String oldCode ,int a, int b){
    StringBuilder sb1 = new StringBuilder(sb); //we build out a new StringBuilder each time
    int k =0;
    if("1011".equals(oldCode) && "1021".equals(sb1.toString()) {
        k = a+b;
    }
    return k;
}
于 2013-03-28T19:53:35.427 回答
1

问题getTotal在于另一个线程可能会更改StringBuilder参数。但你已经知道了。StringBuilder通常实现为具有两个字段的类,比如说int countchar[] value。Java 内存模型不保证从一个线程对这些变量的更改将以相同的顺序发布到其他线程。这使得它甚至可以拥有countvalue.length某些线程更大的线程。在这种情况下,您可能会看到异常。这意味着即使您不关心StringBuilder. 在另一种情况下count,即使它对修改线程具有不同的值,也可能被观察为 0(例如)。这将导致意想不到的结果。

于 2013-03-28T20:09:18.430 回答