String类中的线程安全性

18

以下方法中,从本地变量使用String类构建字符串是否是线程安全的?假设以下方法将被多个线程调用。

public static string WriteResult(int value, string name)
{
    return string.Format("Result: value={0} name={1}", value, name);
}

public static string WriteResult2(int value, string name)
{
    return "Result: value=" + value + " name=" + name;
}

我是否需要使用StringBuilder来确保线程安全?

3个回答

20

完全没问题。除了字符串文字外,两个代码片段中都没有共享状态。由于字符串是不可变的,所以可以自由地在线程之间共享字符串,并且string.Formatstring.Concat(在第二个代码片段中隐式调用)都是线程安全的。

即使其中一个参数是可变的,并且该方法改变了参数,例如:

public static void AddResult(int value, List<string> results)
{
    results.Add("Value " + value);
}

如果多个线程没有引用相同的 List<string>,那么该方法本身仍然是线程安全的。如果多个线程引用相同的 List<string>,即使只是从列表中读取,它也会是不安全的,因为另一个线程可能正在对其进行更改。


18

在这个方法中,intstring参数都是有效的不可变对象,不能被外部代码更改。

因此,在这种情况下,无需关心Format方法或String.Concat的线程安全性。


假设我们有一个可变的类MyObject,可以从外部进行更改:

public class MyClass
{
    public Int32 value1 { get; set; }
    public String value2 { get; set;}
} 

public static string WriteResult2(MyObject obj)
{
    return "Result: value=" + obj.value1 + " name=" + obj.value2 ;
}

在这种情况下,无论是第一种方法还是第二种方法,都可能返回不一致的值(如果一个值已经被放入输出后,value1和value2都被更改)。
正如@JonSkeet所指出的那样,实际上并不是方法本身不安全,而是类本身不安全,在不同线程之间共享时不安全。
要处理这种情况,您将需要创建特殊的线程安全实例方法:
public class MyClass
{
    private Object lockObj = new Object();
    public Int32 value1
    {
        get
        {
            lock (this.lockObj) { ... });
        }
        set
        {
            lock (this.lockObj) { ... });
        }
    }
    public String value2
    {
        get
        {
            lock (this.lockObj) { ... });
        }
        set
        {
            lock (this.lockObj) { ... });
        }
    }

    public string WriteResult2()
    {
        lock (this.lockObj)
        {
             return "Result: value=" + this.value1 + " name=" + this.value2 ;
        }
    }
} 

使用方法中对这些实例进行额外的锁定。显然,第一个类内方法更少出错,但可能会降低性能并创建大量样板代码。理想情况下,在并发编程中,您需要关心共享可变状态及其一致性的越少越好

7
我不会说你后面的情况使得这个"方法"线程不安全——在我看来,它使得类(MyClass)在多个线程之间共享实例是不安全的,这是一个稍微不同的问题。 - Jon Skeet
1
@JonSkeet 谢谢。忘记添加锁。 - Eugene Podskal
1
一个更简单的解决方案是在文档中声明实例成员不是线程安全的,这是很典型的情况。很少有类型真正需要在线程之间共享 - 正如我在我的答案中所说的那样,这并不意味着使用这些类型作为参数的方法不是线程安全的,只要传递给方法的值不是共享的...这是调用代码的问题。 - Jon Skeet
1
虽然我们在属性的getter和setter中已经有锁了,但是我们是否真的需要在WriteResult2方法内部再加锁呢? - Jenish Rabadiya
2
@JenishRabadiya 我们需要确保在 WriteResult 方法内部,value1value2 的值必须保持一致 - 在构建输出字符串时不能更改这些值。例如,当 value1=Jenish,value2=Rabadiya 时,我们调用 WriteResult,它会写入 Jenish,但是外部代码同时将 value2 更改为 Podskal。显然这不是我们想要的结果。尽管在调用 WriteResult 之前允许我们创建这种不一致的状态,但我们应该删除两个 setter 并为这两个属性创建一个同步 setter 方法。或者使用不可变类型。 - Eugene Podskal
显示剩余2条评论

5

这两种方法都是线程安全的,因为在WriteResult内部做什么并不重要。只要它不使用可变静态状态,并且其参数不能从外部更改,您的静态方法就是线程安全的。

很容易验证这些方法不使用静态状态。还很容易验证方法的参数不能从外部更改:

  • value无法更改,因为它是原始类型,并且按值传递
  • name无法更改,因为string对象是不可变的。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接