并发线程中的死锁

多线程中不可避免的要对共享数据共享类等进行操作,但为了让同一时间只有一个线程访问该代码块,引入了锁的概念。

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) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁程序出现的一种假死现象。多线程争抢同一把锁,造成的程序假死现象。死锁现象出现在同步代码块的嵌套形式。其实我们还可以用一张图来表示。

并发线程中的死锁_第1张图片

当前有左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();
        }

我们来看一下效果
并发线程中的死锁_第2张图片

可以看到虽然我的程序里的控制条件是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中的类是不会相互干扰的,因为他们没有公共数据,而且锁也是私有的。意味着另一个类根本无法使用到另一个类里的锁。
但是我运行了两次结果是:
第一次 程序刚运行就 不动了:
并发线程中的死锁_第3张图片

第二次 程序运行了两三秒就不动了:
并发线程中的死锁_第4张图片

对于并发线程,这一点很好解释。因为两个线程的执行顺序是随机的,所以当两个线程恰好到达某一个状态时的时间也会是随机的。这就是为什么很多程序的死锁潜伏期很长,甚至在程序正常的开发中都没有发生过,结果项目一上线,程序死锁了。这时候,项目经理的表情一定是笑(xiang)呵(sha)呵(si)哒(ni)…

你可能感兴趣的:(多线程,并发,死锁)