明永成

导航

 
NET面试题:C#中的lock关键字有何作用
2011-03-01 08:42:04

更多.net面试题,.net电子书,.net教学视频请参考"51CTO提醒您,请勿滥发广告!"

NET面试题:C#中的lock关键字有何作用
作为C#的程序员来说,在遇到线程同步的需求时最常用的就是lock关键字。但如何正确并有效地使用lock却是能否高效地达到同步要求的关键。正因为如此,程序员需要完全理解lock究竟为程序做了什么。
  所涉及的知识点
· lock的等效代码
· System.Threading.Monitor类型的作用和使用方法
  分析问题
1.lock的等效代码
在.NET的多线程程序中,经常会遇到lock关键字来控制同步,比如下列代码:
private object o = new object();
public void Work()
{
lock(o)
{
  //做一些需要线程同步的工作
}
}
事实上,lock这个关键字是C#为方便程序员而定义的语法,它等效于安全地使用System.Threading.Monitor类型。上面的代码就直接等效于下面的代码:
private object o = new object();
public void Work()
{
//这里很重要,是为了避免直接使用私有成员o,而导致线程不安全
Object temp = o;
System.Threading.Monitor.Enter(temp);
try
{
  //做一些需要线程同步的工作
}
finally
{
  System.Threading.Monitor.Exit(temp);
}
}
正如读者所看到的,真正实现了线程同步功能的,就是System.Threading.Monitor类型,lock关键字只是用来代替调用Enter、Exit方法,并且将所有的工作包含在try块内以保证其最终退出同步。
2.System.Threading.Monitor类型的作用和使用
在前文中笔者已经提到了,Monitor类型的Enter和Exit方法用来实现进入和退出对象的同步。具体来说,当Enter方法被调用时,对象的同步索引将被检查,并且.NET将负责一系列的后续工作来保证对象访问时线程的同步,而Exit方法的调用,则保证了当前线程释放该对象的同步块。
代码7-13演示了如何利用lock关键字(也就是Monitor类型)来实现线程同步,具体定义了一个包含需要同步执行方法的类型。
代码7-13  线程同步:UseLock.cs
    /// <summary>
    /// 演示同步锁
    /// </summary>
    public class Lock
    {
        //用来在静态方法中同步
        private static Object o1 = new object();
        //用来在成员方法中不同
        private Object o2 = new object();
        //成员变量
        private static int i1 = 0;
        private int i2 = 0;
        /// <summary>
        /// 测试静态方法的同步
        /// </summary>
        /// <param name="state">状态对象</param>
        public static void Increment1(Object state)
        {
            lock (o1)
            {
                Console.WriteLine("i1的值为:{0}", i1.ToString());
                //这里刻意制造线程并行机会
                //来检查同步的功能
                Thread.Sleep(200);
                i1++;
                Console.WriteLine("i1自增后为:{0}", i1.ToString());
            }
        }
        /// <summary>
        /// 测试成员方法的同步
        /// </summary>
        /// <param name="state">状态对象</param>
        public void Increment2(Object state)
        {
            lock (o2)
            {
                Console.WriteLine("i2的值为:{0}", i2.ToString());
                //这里刻意制造线程并行机会
                //来检查同步的功能
                Thread.Sleep(200);
                i2++;
                Console.WriteLine("i2自增后为:{0}", i2.ToString());
            }
        }      
    }
这样,在main方法中调用该类型对象的方法和其静态方法,测试其同步的效果,如代码7-14所示。
代码7-14  线程同步:UseLock.cs
/// <summary>
/// 程序入口
/// </summary>
class MainClass
{
    /// <summary>
    /// 测试同步效果
    /// </summary>
    static void Main(string[] args)
    {
        //开始多线程
        Console.WriteLine("开始测试静态方法的同步");
        for (int i = 0; i < 5; i++)
        {
            Thread t = new Thread(Lock.Increment1);
            t.Start();
        }
        //这里等待线程执行结束
        Thread.Sleep(5*1000);
        Console.WriteLine("开始测试成员方法的同步");
        Lock l = new Lock();
        //开始多线程
        for (int i = 0; i < 5; i++)
        {
            Thread t = new Thread(l.Increment2);
            t.Start();
        }
        Console.Read();
    }
}
下面是程序的执行结果:
开始测试静态方法的同步
i1的值为:0
i1自增后为:1
i1的值为:1
i1自增后为:2
i1的值为:2
i1自增后为:3
i1的值为:3
i1自增后为:4
i1的值为:4
i1自增后为:5
开始测试成员方法的同步
i2的值为:0
i2自增后为:1
i2的值为:1
i2自增后为:2
i2的值为:2
i2自增后为:3
i2的值为:3
i2自增后为:4
i2的值为:4
i2自增后为:5
可以看到,线程同步被很好地保证了。这里需要强调的是,线程同步本身违反了多线程并行运行的原则,所以读者在使用线程同步时应该尽量做到把lock加在最小的程序块上。如果一个方法有大量的代码需要线程同步,那就需要重新考虑程序的设计了,是否真的有必要进行多线程处理,毕竟线程本身的开销也是相当大的。
对静态方法的同步,一般采用静态私有的引用成员,而对成员方法的同步,一般采用私有的引用成员。读者需要注意静态和非静态成员的使用和把同步对象申明为私有,这都是保证线程同步高效并且正确的关键点。
  答案
C#中的lock关键字实质是调用Monitor.Enter和Monitor.Exit两个方法的简化语法,功能上其实现了进入和退出某个对象的同步。在通常情况下,可以通过lock一个私有的引用成员变量来完成成员方法内的线程同步,而通过lock一个私有的静态引用成员变量来完成静态方法内的线程同步

posted on 2011-03-27 08:16  明永成  阅读(2862)  评论(1编辑  收藏  举报