String class 中的线程安全
Thread safety in String class
像下面的方法一样,使用 String
class 从局部变量构建字符串是否线程安全?假设下面的方法是从多个线程调用的。
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
来保证线程安全?
int
和 string
作为此方法中的参数实际上是不可变的,不能被外部代码更改。
因此,在这种情况下,Format method or String.Concat 无需关心线程安全。
但是让我们假设我们有一些 class 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 ;
}
在这种情况下,无论使用第一种方法还是第二种方法,您都可以 return 不一致的值(在一个值已经输出后,值 1 和值 2 都发生了变化。)
作为@JonSkeet ,实际上并不是方法本身不安全,而是class本身在不同线程之间以这种方式共享是不安全的。
要处理这种情况,您必须创建特殊的线程安全实例方法:
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 ;
}
}
}
或者在使用它的方法中对此类实例使用一些额外的锁定。第一种 in-class 方法显然不太容易出错,但可能会降低性能并创建大量样板代码。理想情况下,在并发编程中,您越不需要关心共享可变状态及其一致性 the better.
太好了。除了字符串文字之外,任何一段代码都没有共享状态。由于字符串是不可变的,因此可以在线程之间自由共享字符串,并且 string.Format
和 string.Concat
(在第二段代码中隐式调用)都是线程安全的。
即使参数之一是可变的,即使方法改变了参数,例如
public static void AddResult(int value, List<string> results)
{
results.Add("Value " + value);
}
... 那么方法本身仍然是线程安全的,只要多个线程不引用同一个 List<string>
。如果多个线程 did 引用相同的 List<string>
那么即使它只是 read 从列表中作为另一个线程也是不安全的可能会发生变异。
这两种方法都是线程安全的,因为在 WriteResult
中做什么并不重要。只要它不使用可变静态,并且它的参数不能从外部更改,你的静态方法就是线程安全的。
很容易验证这些方法没有使用静态。验证方法的参数不能从外部更改也很容易:
value
不能改,因为是原始类型,传值
name
无法更改,因为 string
对象是不可变的。
像下面的方法一样,使用 String
class 从局部变量构建字符串是否线程安全?假设下面的方法是从多个线程调用的。
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
来保证线程安全?
int
和 string
作为此方法中的参数实际上是不可变的,不能被外部代码更改。
因此,在这种情况下,Format method or String.Concat 无需关心线程安全。
但是让我们假设我们有一些 class 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 ;
}
在这种情况下,无论使用第一种方法还是第二种方法,您都可以 return 不一致的值(在一个值已经输出后,值 1 和值 2 都发生了变化。)
作为@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 ;
}
}
}
或者在使用它的方法中对此类实例使用一些额外的锁定。第一种 in-class 方法显然不太容易出错,但可能会降低性能并创建大量样板代码。理想情况下,在并发编程中,您越不需要关心共享可变状态及其一致性 the better.
太好了。除了字符串文字之外,任何一段代码都没有共享状态。由于字符串是不可变的,因此可以在线程之间自由共享字符串,并且 string.Format
和 string.Concat
(在第二段代码中隐式调用)都是线程安全的。
即使参数之一是可变的,即使方法改变了参数,例如
public static void AddResult(int value, List<string> results)
{
results.Add("Value " + value);
}
... 那么方法本身仍然是线程安全的,只要多个线程不引用同一个 List<string>
。如果多个线程 did 引用相同的 List<string>
那么即使它只是 read 从列表中作为另一个线程也是不安全的可能会发生变异。
这两种方法都是线程安全的,因为在 WriteResult
中做什么并不重要。只要它不使用可变静态,并且它的参数不能从外部更改,你的静态方法就是线程安全的。
很容易验证这些方法没有使用静态。验证方法的参数不能从外部更改也很容易:
value
不能改,因为是原始类型,传值name
无法更改,因为string
对象是不可变的。