1: 考虑下下面的代码:
class
ThreadUnsafe
{
static
int
_val1
=
1
, _val2
=
1
;
internal
static
void
Go()
{
if
(_val2
!=
0
)
{
Console.WriteLine(_val1
/
_val2);
}
_val2
=
0
;
}
}
这段代码是非线程安全的,假设有两个线程A,B,A,B都执行到了Go方法的if判断中,假设_val2=1.所以两个线程A,B都通过if判断,
A执行了Console.WriteLine方法,然后退出if语句,执行_val2=0,此时_val2=0.
但是此时线程B才刚刚执行到Console.WriteLine方法,而此时_val2=0.所以你有可能会得到一个divide by zero 的异常。
为了保证线程安全,我们可以使用Lock关键字,例如:
static
readonly
object
_locker
=
new
object
();
static
int
_val1
=
1
, _val2
=
1
;
internal
static
void
Go()
{
lock
(_locker)
{
if
(_val2
!=
0
)
{
Console.WriteLine(_val1
/
_val2);
}
_val2
=
0
;
}
}
此时线程A,B都只能有一个可以获得_locker锁,所以只能有一个线程来执行lock块的代码。
C#的Lock关键字实际上是Monitor.Enter,和Monitor.Exit的缩写。例如上面的代码和下面的等价。
Monitor.Enter(_locker);
try
{
if
(_val2
!=
0
)
{
Console.WriteLine(_val1
/
_val2);
}
_val2
=
0
;
}
finally
{ Monitor.Exit(_locker); }
如果在调用Monitor.Exit之前没有调用Monitor.Enter,则会抛出一个异常。
不知道大家注意到没有,在Monitor.Enter 和 Try 方法之间可能会抛出异常。
例如在线程上调用Abort,或者是OutOfMemoryException。
为了解决这个问题CLR 4.0提供了Monitor.Enter的重载,增加了lockTaken 字段,当Monitor.Enter成功获取锁之后,lockTaken就是True,否则为False。
我们可以将上面的代码改成下面的版本。
bool
lockTaken
=
false
;
try
{
Monitor.Enter(_locker,
ref
lockTaken);
//
Do something..
}
finally
{
if
(lockTaken) {
Monitor.Exit(_locker);
}
}
Monitor也提供了TryEnter方法,并且可以传递一个超时时间。如果方法返回True,则代表获取了锁,否则为false。
2:选择同步对象。
Monitor.Enter方法的参数是一个object类型,所以任何对象都可以是同步对象,考虑下下面的代码:
-
int i=5; lock(i){} //
锁定值类型
-
lock(this){} //
锁定
this
对象
-
lock(typeof(Product)){} //
锁定
type
对象。
-
string str="dddd"; lock(str){} //
锁定字符串
1:锁定值类型会将值类型进行装箱,所以Monitor.Enter进入的是一个对象,但是Monitor.Exit()退出的是另一个不同的对象。
2,3:锁定this和type对象,会导致无法控制锁的逻辑,并且它很难保证不死锁和频繁的阻塞,在相同进程中锁定type对象会穿越应用程序域。
4:由于字符串驻留机制,所以也不要锁定string,关于这点,请大家去逛一逛老A的博客。
3:嵌套锁:
同一个线程可以多次锁定同一对象。例如
lock
(locker)
lock
(locker)
lock
(locker)
{
//
do something
}
或者是:
Monitor.Enter(locker); Monitor.Enter(locker); Monitor.Enter(locker);
//
Do something.
Monitor.Exit(locker); Monitor.Exit(locker); Monitor.Exit(locker);
当一个线程使用一个锁调用另一方法的时候,嵌套锁就非常的有用。例如:
static
readonly
object
_locker
=
new
object
();
static
void
Main()
{
lock
(_locker)
{
AnotherMethod();
}
}
static
void
AnotherMethod()
{
lock
(_locker){
//
dosomething;}
}
4:死锁:
先看下面的代码:
static
object
locker1
=
new
object
();
static
object
locker2
=
new
object
();
public
static
void
MainThread()
{
new
Thread(()
=>
{
lock
(locker1) //获取锁locker1
{
Thread.Sleep(
1000
);
lock
(locker2) //尝试获取locker2
{
Console.WriteLine(
"
locker1,locker2
"
);
}
}
}).Start();
lock
(locker2) //获取锁locker2
{
Thread.Sleep(
1000
);
lock
(locker1) //尝试获取locker1
{
Console.WriteLine(
"
locker2,locker1
"
);
}
}
}
在这里
主线程先获取locker2的锁,然后sleep,接着尝试获取locker1的锁。
副线程先获取locker1的锁,然后sleep,接着尝试获取locker2的锁。
程序进入了死锁状态,两个线程都在等待对方释放自己等待的锁。
CLR作为一个独立宿主环境,它不像SQL Server一样,它没有自动检测死锁机制,也不会结束一个线程来破坏死锁。死锁的线程会导致部分线程无限的等待。
下篇文章会介绍一些其他同步构造。
参考资料:
http://www.albahari.com/threading/
CLR Via C# 3.0