多线程中不可避免的要对共享数据共享类等进行操作,但为了让同一时间只有一个线程访问该代码块,引入了锁的概念。
C# 中锁的原型是这样的
lock (x)
{
DoSomething();
}
首先 为什么上面这段话能够锁定代码?最关键的就是这个X对象。事实上X 是任意一种引用类型。它在这起的作用就是任何线程执行到lock(x)的时候,X需要独享才能运行下面的代码,若假定现在有3个线程A,B,C都执行到了lock(X)而ABC因为此时都占有X,这时ABC就要停下来排个队, 一个一个使用X,从而起到在下面的代码块内只有一个线程在运行(因为此时只有一个线程独享X,其余两个在排队),所以这个X必须是所有要执行临界区域代码 进程必须共有的一个资源,从而起到抑制线程的作用。
说到锁 ,接下来我们还有必要谈谈死锁。
我们都知道产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
死锁程序出现的一种假死现象。多线程争抢同一把锁,造成的程序假死现象。死锁现象出现在同步代码块的嵌套形式。其实我们还可以用一张图来表示。
当前有左L右R两个线程,在每隔线程里都有两个嵌套形式。
左边外层我们用锁A 里层用锁B .右边外层用锁B 里层用锁A
这样一来,我们可以看到左右两个小人分别拿到AB锁进入左右两个程序 外层后,再进入里层程序的时候 左边的需要B钥匙但是他没有,只能等待。右边的需要A钥匙,但是他也没有,也只能等待。如此,悲剧发生了。。
对应上面的图,简单写了一个这样的程序
public class DeadLock
{
private bool flag;
static object lockKeyA = new object();
static object lockKeyB = new object();
//定义一个标记,标记线程A还是B先进
public DeadLock(bool flag) { this.flag = flag; }
public void run()
{
while (true)
{ //判断变量的值是true 进入A门 否则B门
if (flag)
{
lock (lockKeyA)
{
Console.WriteLine("A门在外:当前已经进入A门等待B门钥匙");
lock (lockKeyB)
{
Console.WriteLine("A门在外:当前已经进入A门并进入B门");
}
}
}
else
{
lock (lockKeyB)
{
Console.WriteLine("B门在外:当前已经进入B门等待A门钥匙");
lock (lockKeyA)
{
Console.WriteLine("B门在外:当前已经进入B门并进入A门");
}
}
}
}
}
}
调用时:
private void Form1_Load(object sender, EventArgs e)
{
DeadLock dealLockA = new DeadLock(true);
DeadLock dealLockB = new DeadLock(false);
Thread t1 = new Thread(dealLockA.run);
Thread t2 = new Thread(dealLockB.run);
t1.Start();
t2.Start();
}
我们来看一下效果
可以看到虽然我的程序里的控制条件是while(true) 但是在程序运行的过一段时间后它已经自己锁死停止了。这一时刻,我们看到A门在外的那个线程 当前已经进入A门等待B门钥匙。B门在外的那个线程,当前已经进入B门等待A门钥匙。程序假死了。。以上完全符合我们在上图中进行的分析。
此外,对上面的内容, 还有一点要注意。我们说锁的lock(X)中X是一种对象引用类型。
也就是说假如我定义了两个锁,String strLockA=”123” String strLockB=”123” 他们都是指向同一个对象,那么这两把锁是一个。
我做了一个这样的例子。类LockClass1和LockClass2中分别定义了两个私有的锁。仍然是嵌套锁。
类LockClass1:
/// <summary>
/// <summary>
/// 锁是引用类型验证类1-马丹妹-2015年11月30日
/// </summary>
/// </summary>
public class LockClass1
{
String strLock1 = "123";
String strLock2 = "456";
public void TestLock()
{
while (true)
{
lock (strLock1)
{
Console.WriteLine("LockClass1 中执行到strLock1");
lock (strLock2)
{
Console.WriteLine("LockClass1 中执行到strLock2");
}
}
}
}
}
类LockClass2
/// <summary>
/// 锁是引用类型验证类2-马丹妹-2015年11月30日
/// </summary>
public class LockClass2
{
String strLock3 = "123";
String strLock4 = "456";
public void TestLock()
{
while (true)
{
lock (strLock4)
{
Console.WriteLine("LockClass2 中执行到strLock4");
lock (strLock3)
{
Console.WriteLine("LockClass2 中执行到strLock3");
}
}
}
}
}
调用时:
private void Form1_Load(object sender, EventArgs e)
{
LockClass1 lockClass1 = new LockClass1();
LockClass2 lockClass2 = new LockClass2();
Thread thread1 = new Thread(lockClass1.TestLock);
Thread thread2 = new Thread(lockClass2.TestLock);
thread1.Start();
thread2.Start();
}
按我们往常的理解,LockClass1 和LockClass2中的类是不会相互干扰的,因为他们没有公共数据,而且锁也是私有的。意味着另一个类根本无法使用到另一个类里的锁。
但是我运行了两次结果是:
第一次 程序刚运行就 不动了:
第二次 程序运行了两三秒就不动了:
对于并发线程,这一点很好解释。因为两个线程的执行顺序是随机的,所以当两个线程恰好到达某一个状态时的时间也会是随机的。这就是为什么很多程序的死锁潜伏期很长,甚至在程序正常的开发中都没有发生过,结果项目一上线,程序死锁了。这时候,项目经理的表情一定是笑(xiang)呵(sha)呵(si)哒(ni)…