在学习线程安全之前,我们需要了解两个概念,什么叫做本地和共享。
1)本地独立:
CLR会为每个线程分配自己的内存栈,以便使本地变量保持独立。
2)共享:
如果多个线程引用了同一个对象的实例,那么它们就共享了数据。
class ThreadTest
{
bool _done;
static void Main()
{
ThreadTest tt = new ThreadTest();//创建了一个共同的实例
new Thread(tt.Go).Start();
tt.Go();
}
void Go()//实例方法
{
if (!_done)
{
_done = true;
Console.WriteLine("Done");
}
}
}
在上例中,我们可以看到主线程使用了ThreadTest的同一个实例,这里的_done变量是共享的。
此外,被Lambda表达式或者匿名委托所捕获的本地变量,会被编译器转换成字段,也会被共享,静态字段也会在线程间共享。
class ThreadTest
{
static void Main()
{
bool done = false;
ThreadStart action = () =>
{
if (!done)
{
done = true;
Console.WriteLine("Done");
}
};
new Thread(action).Start();
action();
}
}
这里的done变量是共享的。
在了解了本地和共享状态后,我们可以引出一个新的概念即线程安全。
在第一个案例debug之后,我们可以打印出一个Done,但是假如主线程和新线程在_done变量未变成true之前都进入到if语句了呢?那么,我们会得到两个Done。这样的线程很明显是不安全的。
所以,线程安全指的就是:在多线程的上下文转换中,可以避免不确定性的代码就可以称作线程安全。
如何实现线程安全:
在读取和写入共享数据的时候,通过Lock保证,两个线程同时竞争同一个锁的时候,一个线程会等待或者阻塞,知道这个锁变成可用状态。